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
|
@@ -2,11 +2,13 @@ import asyncio
|
|
|
2
2
|
import inspect
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
import time
|
|
5
6
|
from collections.abc import Callable
|
|
6
7
|
from concurrent.futures import ThreadPoolExecutor
|
|
7
8
|
from datetime import UTC, datetime
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
11
|
+
from anyio.to_thread import current_default_thread_limiter
|
|
10
12
|
from fastapi import (
|
|
11
13
|
APIRouter,
|
|
12
14
|
BackgroundTasks,
|
|
@@ -52,6 +54,7 @@ from reflexio.models.api_schema.retriever_schema import (
|
|
|
52
54
|
GetEvaluationResultsViewResponse,
|
|
53
55
|
GetInteractionsRequest,
|
|
54
56
|
GetInteractionsViewResponse,
|
|
57
|
+
GetLearningProvenanceRequest,
|
|
55
58
|
GetPlaybookApplicationStatsRequest,
|
|
56
59
|
GetPlaybookApplicationStatsResponse,
|
|
57
60
|
GetProfileStatisticsResponse,
|
|
@@ -61,6 +64,7 @@ from reflexio.models.api_schema.retriever_schema import (
|
|
|
61
64
|
GetUserPlaybooksRequest,
|
|
62
65
|
GetUserPlaybooksViewResponse,
|
|
63
66
|
GetUserProfilesRequest,
|
|
67
|
+
LearningProvenanceViewResponse,
|
|
64
68
|
ProfileChangeLogViewResponse,
|
|
65
69
|
RequestDataView,
|
|
66
70
|
RerankUserProfilesRequest,
|
|
@@ -74,6 +78,7 @@ from reflexio.models.api_schema.retriever_schema import (
|
|
|
74
78
|
SearchUserProfileRequest,
|
|
75
79
|
SessionView,
|
|
76
80
|
SetConfigResponse,
|
|
81
|
+
SourceUserPlaybookProvenanceView,
|
|
77
82
|
StorageStatsRequest,
|
|
78
83
|
StorageStatsResponse,
|
|
79
84
|
UnifiedSearchRequest,
|
|
@@ -153,10 +158,16 @@ from reflexio.models.api_schema.ui.converters import (
|
|
|
153
158
|
to_user_playbook_view,
|
|
154
159
|
)
|
|
155
160
|
from reflexio.models.config_schema import (
|
|
161
|
+
DEFAULT_WINDOW_SIZE,
|
|
156
162
|
SINGLETON_AGENT_SUCCESS_EVALUATION_NAME,
|
|
157
163
|
Config,
|
|
158
164
|
)
|
|
159
|
-
from reflexio.server._auth import
|
|
165
|
+
from reflexio.server._auth import (
|
|
166
|
+
DEFAULT_ORG_ID,
|
|
167
|
+
default_billing_gate,
|
|
168
|
+
default_get_caller_type,
|
|
169
|
+
default_get_org_id,
|
|
170
|
+
)
|
|
160
171
|
from reflexio.server.api_endpoints import (
|
|
161
172
|
account_api,
|
|
162
173
|
health_api,
|
|
@@ -172,22 +183,34 @@ from reflexio.server.correlation import correlation_id_var, generate_correlation
|
|
|
172
183
|
from reflexio.server.operation_limiter import (
|
|
173
184
|
OperationName,
|
|
174
185
|
limiter_http_exception,
|
|
186
|
+
log_publish_hardware_capacity,
|
|
175
187
|
run_with_operation_limit,
|
|
176
188
|
)
|
|
177
|
-
from reflexio.server.services.agent_success_evaluation.group_evaluation_runner import (
|
|
178
|
-
run_group_evaluation,
|
|
179
|
-
)
|
|
180
189
|
from reflexio.server.services.agent_success_evaluation.regen_jobs import (
|
|
181
190
|
REGEN_JOBS,
|
|
182
191
|
run_regen,
|
|
183
192
|
)
|
|
193
|
+
from reflexio.server.services.agent_success_evaluation.runner import (
|
|
194
|
+
run_group_evaluation,
|
|
195
|
+
)
|
|
196
|
+
from reflexio.server.services.extractor_interaction_utils import (
|
|
197
|
+
get_effective_source_filter,
|
|
198
|
+
get_extractor_window_params,
|
|
199
|
+
)
|
|
200
|
+
from reflexio.server.tracing import profile_step
|
|
184
201
|
|
|
185
202
|
logger = logging.getLogger(__name__)
|
|
186
203
|
|
|
187
204
|
# Re-exported for backwards compatibility — callers that did
|
|
188
205
|
# ``from reflexio.server.api import default_get_org_id`` or ``DEFAULT_ORG_ID``
|
|
189
206
|
# continue to work.
|
|
190
|
-
__all__ = [
|
|
207
|
+
__all__ = [
|
|
208
|
+
"DEFAULT_ORG_ID",
|
|
209
|
+
"create_app",
|
|
210
|
+
"default_billing_gate",
|
|
211
|
+
"default_get_caller_type",
|
|
212
|
+
"default_get_org_id",
|
|
213
|
+
]
|
|
191
214
|
|
|
192
215
|
# Bot protection configuration
|
|
193
216
|
REQUEST_TIMEOUT_SECONDS = 60
|
|
@@ -255,8 +278,41 @@ def get_rate_limit_key(request: Request) -> str:
|
|
|
255
278
|
return get_remote_address(request)
|
|
256
279
|
|
|
257
280
|
|
|
281
|
+
def _storage_backend_name(limiter_obj: Limiter) -> str:
|
|
282
|
+
storage = getattr(limiter_obj, "_storage", None)
|
|
283
|
+
if storage is None:
|
|
284
|
+
return "unknown"
|
|
285
|
+
return storage.__class__.__name__
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _trace_external_rate_limit_backend(limiter_obj: Limiter) -> None:
|
|
289
|
+
"""Trace rate-limit storage hits when the backend is an external service."""
|
|
290
|
+
backend = _storage_backend_name(limiter_obj)
|
|
291
|
+
if backend == "MemoryStorage":
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
strategy = getattr(limiter_obj, "limiter", None)
|
|
295
|
+
if strategy is None or getattr(strategy, "_reflexio_traced", False):
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
original_hit = strategy.hit
|
|
299
|
+
|
|
300
|
+
def traced_hit(item: Any, *identifiers: str, cost: int = 1) -> bool:
|
|
301
|
+
with profile_step(
|
|
302
|
+
"rate_limit.backend_hit",
|
|
303
|
+
storage_backend=backend,
|
|
304
|
+
strategy=strategy.__class__.__name__,
|
|
305
|
+
cost=cost,
|
|
306
|
+
):
|
|
307
|
+
return original_hit(item, *identifiers, cost=cost)
|
|
308
|
+
|
|
309
|
+
strategy.hit = traced_hit
|
|
310
|
+
strategy._reflexio_traced = True
|
|
311
|
+
|
|
312
|
+
|
|
258
313
|
# Initialize rate limiter
|
|
259
314
|
limiter = Limiter(key_func=get_rate_limit_key)
|
|
315
|
+
_trace_external_rate_limit_backend(limiter)
|
|
260
316
|
|
|
261
317
|
|
|
262
318
|
def _run_limited_api[T](
|
|
@@ -448,6 +504,16 @@ class CorrelationIdMiddleware(BaseHTTPMiddleware):
|
|
|
448
504
|
) -> Response:
|
|
449
505
|
cid = generate_correlation_id()
|
|
450
506
|
correlation_id_var.set(cid)
|
|
507
|
+
try:
|
|
508
|
+
stats = current_default_thread_limiter().statistics()
|
|
509
|
+
request.state.tp_borrowed = stats.borrowed_tokens
|
|
510
|
+
request.state.tp_total = stats.total_tokens
|
|
511
|
+
request.state.tp_waiting = stats.tasks_waiting
|
|
512
|
+
except Exception as exc: # noqa: BLE001
|
|
513
|
+
logger.debug("Failed to snapshot threadpool limiter stats: %s", exc)
|
|
514
|
+
request.state.tp_borrowed = None
|
|
515
|
+
request.state.tp_total = None
|
|
516
|
+
request.state.tp_waiting = None
|
|
451
517
|
response = await call_next(request)
|
|
452
518
|
response.headers["X-Correlation-ID"] = cid
|
|
453
519
|
return response
|
|
@@ -456,6 +522,86 @@ class CorrelationIdMiddleware(BaseHTTPMiddleware):
|
|
|
456
522
|
core_router = APIRouter()
|
|
457
523
|
|
|
458
524
|
|
|
525
|
+
def _stamp_search_dependencies_done(request: Request) -> None:
|
|
526
|
+
"""Stamp when dependency resolution reaches its final search dependency."""
|
|
527
|
+
request.state.search_deps_done_monotonic = time.monotonic()
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _meter_applied_learnings(
|
|
531
|
+
*,
|
|
532
|
+
org_id: str,
|
|
533
|
+
caller_type: str,
|
|
534
|
+
surfaced_count: int,
|
|
535
|
+
request_id: str | None = None,
|
|
536
|
+
session_id: str | None = None,
|
|
537
|
+
) -> None:
|
|
538
|
+
"""Emit the ③ Application event via the OSS emission helper.
|
|
539
|
+
|
|
540
|
+
No-op unless a production-agent caller surfaced >= 1 result. The cheap
|
|
541
|
+
caller-type and count guard runs first so the get_reflexio / config lookup
|
|
542
|
+
is skipped on the free paths (dashboard / empty result).
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
org_id: Organization ID for the requesting caller.
|
|
546
|
+
caller_type: Resolved caller classification (e.g. ``"production_agent"``).
|
|
547
|
+
surfaced_count: Total number of learnings returned to the caller.
|
|
548
|
+
request_id: Optional request correlation ID from the payload.
|
|
549
|
+
session_id: Optional session ID from the payload.
|
|
550
|
+
"""
|
|
551
|
+
if caller_type != "production_agent" or surfaced_count <= 0:
|
|
552
|
+
return
|
|
553
|
+
try:
|
|
554
|
+
from reflexio.server.billing_meter import record_applied_learnings
|
|
555
|
+
from reflexio.server.billing_signals import platform_llm_from_config
|
|
556
|
+
|
|
557
|
+
config = get_reflexio(org_id=org_id).request_context.configurator.get_config()
|
|
558
|
+
record_applied_learnings(
|
|
559
|
+
org_id=org_id,
|
|
560
|
+
surfaced_count=surfaced_count,
|
|
561
|
+
caller_type=caller_type,
|
|
562
|
+
platform_llm=platform_llm_from_config(config),
|
|
563
|
+
platform_storage=None, # resolved enterprise-side at rollup (Phase 1)
|
|
564
|
+
request_id=request_id,
|
|
565
|
+
session_id=session_id,
|
|
566
|
+
)
|
|
567
|
+
except Exception:
|
|
568
|
+
logger.warning(
|
|
569
|
+
"applied-learnings metering failed for org %s", org_id, exc_info=True
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def _meter_search_request(
|
|
574
|
+
*,
|
|
575
|
+
org_id: str,
|
|
576
|
+
caller_type: str,
|
|
577
|
+
request_id: str | None = None,
|
|
578
|
+
session_id: str | None = None,
|
|
579
|
+
) -> None:
|
|
580
|
+
"""Emit one production-agent search request metric.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
org_id: Organization ID for the requesting caller.
|
|
584
|
+
caller_type: Resolved caller classification.
|
|
585
|
+
request_id: Optional request correlation ID from the payload.
|
|
586
|
+
session_id: Optional session ID from the payload.
|
|
587
|
+
"""
|
|
588
|
+
if caller_type != "production_agent":
|
|
589
|
+
return
|
|
590
|
+
try:
|
|
591
|
+
from reflexio.server.billing_meter import record_search_request
|
|
592
|
+
|
|
593
|
+
record_search_request(
|
|
594
|
+
org_id=org_id,
|
|
595
|
+
caller_type=caller_type,
|
|
596
|
+
request_id=request_id,
|
|
597
|
+
session_id=session_id,
|
|
598
|
+
)
|
|
599
|
+
except Exception:
|
|
600
|
+
logger.warning(
|
|
601
|
+
"search-request metering failed for org %s", org_id, exc_info=True
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
|
|
459
605
|
@core_router.get("/")
|
|
460
606
|
def root() -> dict[str, str]:
|
|
461
607
|
return {
|
|
@@ -466,7 +612,7 @@ def root() -> dict[str, str]:
|
|
|
466
612
|
|
|
467
613
|
|
|
468
614
|
@core_router.get("/health")
|
|
469
|
-
def health_check() -> dict[str, str]:
|
|
615
|
+
async def health_check() -> dict[str, str]:
|
|
470
616
|
"""Health check endpoint for ECS/container orchestration."""
|
|
471
617
|
return {"status": "healthy"}
|
|
472
618
|
|
|
@@ -534,6 +680,7 @@ def publish_user_interaction(
|
|
|
534
680
|
background_tasks: BackgroundTasks,
|
|
535
681
|
org_id: str = Depends(default_get_org_id),
|
|
536
682
|
wait_for_response: bool = False,
|
|
683
|
+
_gate: None = Depends(default_billing_gate("learnings_generated")), # noqa: B008
|
|
537
684
|
) -> PublishUserInteractionResponse:
|
|
538
685
|
if wait_for_response:
|
|
539
686
|
# Process synchronously so the caller gets the real result
|
|
@@ -551,6 +698,7 @@ def publish_user_interaction(
|
|
|
551
698
|
fn=lambda: publisher_api.add_user_interaction(
|
|
552
699
|
org_id=org_id, request=payload
|
|
553
700
|
),
|
|
701
|
+
wait_forever=False,
|
|
554
702
|
)
|
|
555
703
|
except TimeoutError:
|
|
556
704
|
logger.warning(
|
|
@@ -647,17 +795,33 @@ def search_user_profiles(
|
|
|
647
795
|
request: Request,
|
|
648
796
|
payload: SearchUserProfileRequest,
|
|
649
797
|
org_id: str = Depends(default_get_org_id),
|
|
798
|
+
caller_type: str = Depends(default_get_caller_type),
|
|
799
|
+
_gate: None = Depends(default_billing_gate("application")), # noqa: B008
|
|
650
800
|
) -> SearchProfilesViewResponse:
|
|
651
801
|
response = _run_limited_api(
|
|
652
802
|
org_id,
|
|
653
803
|
"search",
|
|
654
804
|
lambda: get_reflexio(org_id=org_id).search_user_profiles(payload),
|
|
655
805
|
)
|
|
656
|
-
|
|
806
|
+
resp = SearchProfilesViewResponse(
|
|
657
807
|
success=response.success,
|
|
658
808
|
user_profiles=[to_profile_view(p) for p in response.user_profiles],
|
|
659
809
|
msg=response.msg,
|
|
660
810
|
)
|
|
811
|
+
_meter_search_request(
|
|
812
|
+
org_id=org_id,
|
|
813
|
+
caller_type=caller_type,
|
|
814
|
+
request_id=getattr(payload, "request_id", None),
|
|
815
|
+
session_id=getattr(payload, "session_id", None),
|
|
816
|
+
)
|
|
817
|
+
_meter_applied_learnings(
|
|
818
|
+
org_id=org_id,
|
|
819
|
+
caller_type=caller_type,
|
|
820
|
+
surfaced_count=len(resp.user_profiles),
|
|
821
|
+
request_id=getattr(payload, "request_id", None),
|
|
822
|
+
session_id=getattr(payload, "session_id", None),
|
|
823
|
+
)
|
|
824
|
+
return resp
|
|
661
825
|
|
|
662
826
|
|
|
663
827
|
@core_router.post(
|
|
@@ -757,6 +921,8 @@ def search_user_playbooks_endpoint(
|
|
|
757
921
|
request: Request,
|
|
758
922
|
payload: SearchUserPlaybookRequest,
|
|
759
923
|
org_id: str = Depends(default_get_org_id),
|
|
924
|
+
caller_type: str = Depends(default_get_caller_type),
|
|
925
|
+
_gate: None = Depends(default_billing_gate("application")), # noqa: B008
|
|
760
926
|
) -> SearchUserPlaybooksViewResponse:
|
|
761
927
|
"""Search user playbooks with semantic search and advanced filtering.
|
|
762
928
|
|
|
@@ -767,6 +933,7 @@ def search_user_playbooks_endpoint(
|
|
|
767
933
|
request (Request): The HTTP request object (for rate limiting)
|
|
768
934
|
payload (SearchUserPlaybookRequest): The search request
|
|
769
935
|
org_id (str): Organization ID
|
|
936
|
+
caller_type (str): Billing caller classification (injected via dependency).
|
|
770
937
|
|
|
771
938
|
Returns:
|
|
772
939
|
SearchUserPlaybooksViewResponse: Response containing matching user playbooks
|
|
@@ -776,11 +943,25 @@ def search_user_playbooks_endpoint(
|
|
|
776
943
|
"search",
|
|
777
944
|
lambda: get_reflexio(org_id=org_id).search_user_playbooks(payload),
|
|
778
945
|
)
|
|
779
|
-
|
|
946
|
+
resp = SearchUserPlaybooksViewResponse(
|
|
780
947
|
success=response.success,
|
|
781
948
|
user_playbooks=[to_user_playbook_view(rf) for rf in response.user_playbooks],
|
|
782
949
|
msg=response.msg,
|
|
783
950
|
)
|
|
951
|
+
_meter_search_request(
|
|
952
|
+
org_id=org_id,
|
|
953
|
+
caller_type=caller_type,
|
|
954
|
+
request_id=getattr(payload, "request_id", None),
|
|
955
|
+
session_id=getattr(payload, "session_id", None),
|
|
956
|
+
)
|
|
957
|
+
_meter_applied_learnings(
|
|
958
|
+
org_id=org_id,
|
|
959
|
+
caller_type=caller_type,
|
|
960
|
+
surfaced_count=len(resp.user_playbooks),
|
|
961
|
+
request_id=getattr(payload, "request_id", None),
|
|
962
|
+
session_id=getattr(payload, "session_id", None),
|
|
963
|
+
)
|
|
964
|
+
return resp
|
|
784
965
|
|
|
785
966
|
|
|
786
967
|
@core_router.post(
|
|
@@ -793,6 +974,8 @@ def search_agent_playbooks_endpoint(
|
|
|
793
974
|
request: Request,
|
|
794
975
|
payload: SearchAgentPlaybookRequest,
|
|
795
976
|
org_id: str = Depends(default_get_org_id),
|
|
977
|
+
caller_type: str = Depends(default_get_caller_type),
|
|
978
|
+
_gate: None = Depends(default_billing_gate("application")), # noqa: B008
|
|
796
979
|
) -> SearchAgentPlaybooksViewResponse:
|
|
797
980
|
"""Search agent playbooks with semantic search and advanced filtering.
|
|
798
981
|
|
|
@@ -803,6 +986,7 @@ def search_agent_playbooks_endpoint(
|
|
|
803
986
|
request (Request): The HTTP request object (for rate limiting)
|
|
804
987
|
payload (SearchAgentPlaybookRequest): The search request
|
|
805
988
|
org_id (str): Organization ID
|
|
989
|
+
caller_type (str): Billing caller classification (injected via dependency).
|
|
806
990
|
|
|
807
991
|
Returns:
|
|
808
992
|
SearchAgentPlaybooksViewResponse: Response containing matching agent playbooks
|
|
@@ -812,11 +996,25 @@ def search_agent_playbooks_endpoint(
|
|
|
812
996
|
"search",
|
|
813
997
|
lambda: get_reflexio(org_id=org_id).search_agent_playbooks(payload),
|
|
814
998
|
)
|
|
815
|
-
|
|
999
|
+
resp = SearchAgentPlaybooksViewResponse(
|
|
816
1000
|
success=response.success,
|
|
817
1001
|
agent_playbooks=[to_agent_playbook_view(fb) for fb in response.agent_playbooks],
|
|
818
1002
|
msg=response.msg,
|
|
819
1003
|
)
|
|
1004
|
+
_meter_search_request(
|
|
1005
|
+
org_id=org_id,
|
|
1006
|
+
caller_type=caller_type,
|
|
1007
|
+
request_id=getattr(payload, "request_id", None),
|
|
1008
|
+
session_id=getattr(payload, "session_id", None),
|
|
1009
|
+
)
|
|
1010
|
+
_meter_applied_learnings(
|
|
1011
|
+
org_id=org_id,
|
|
1012
|
+
caller_type=caller_type,
|
|
1013
|
+
surfaced_count=len(resp.agent_playbooks),
|
|
1014
|
+
request_id=getattr(payload, "request_id", None),
|
|
1015
|
+
session_id=getattr(payload, "session_id", None),
|
|
1016
|
+
)
|
|
1017
|
+
return resp
|
|
820
1018
|
|
|
821
1019
|
|
|
822
1020
|
@core_router.post(
|
|
@@ -828,7 +1026,11 @@ def search_agent_playbooks_endpoint(
|
|
|
828
1026
|
def unified_search_endpoint(
|
|
829
1027
|
request: Request,
|
|
830
1028
|
payload: UnifiedSearchRequest,
|
|
1029
|
+
background_tasks: BackgroundTasks,
|
|
831
1030
|
org_id: str = Depends(default_get_org_id),
|
|
1031
|
+
caller_type: str = Depends(default_get_caller_type),
|
|
1032
|
+
_gate: None = Depends(default_billing_gate("application")), # noqa: B008
|
|
1033
|
+
_deps_done: None = Depends(_stamp_search_dependencies_done),
|
|
832
1034
|
) -> UnifiedSearchViewResponse:
|
|
833
1035
|
"""Search across all entity types (profiles, agent playbooks, user playbooks).
|
|
834
1036
|
|
|
@@ -840,31 +1042,76 @@ def unified_search_endpoint(
|
|
|
840
1042
|
request (Request): The HTTP request object (for rate limiting)
|
|
841
1043
|
payload (UnifiedSearchRequest): The unified search request
|
|
842
1044
|
org_id (str): Organization ID
|
|
1045
|
+
caller_type (str): Billing caller classification (injected via dependency).
|
|
843
1046
|
|
|
844
1047
|
Returns:
|
|
845
1048
|
UnifiedSearchViewResponse: Combined search results
|
|
846
1049
|
"""
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
lambda: get_reflexio(org_id=org_id).unified_search(payload, org_id=org_id),
|
|
851
|
-
)
|
|
852
|
-
return UnifiedSearchViewResponse(
|
|
853
|
-
success=response.success,
|
|
854
|
-
profiles=[to_profile_view(p) for p in response.profiles],
|
|
855
|
-
agent_playbooks=[to_agent_playbook_view(fb) for fb in response.agent_playbooks],
|
|
856
|
-
user_playbooks=[to_user_playbook_view(rf) for rf in response.user_playbooks],
|
|
857
|
-
reformulated_query=response.reformulated_query,
|
|
858
|
-
msg=response.msg,
|
|
859
|
-
agent_trace=response.agent_trace,
|
|
860
|
-
rehydrated_text=response.rehydrated_text,
|
|
1050
|
+
deps_done = getattr(request.state, "search_deps_done_monotonic", None)
|
|
1051
|
+
deps_to_body_ms = (
|
|
1052
|
+
int((time.monotonic() - deps_done) * 1000) if deps_done is not None else None
|
|
861
1053
|
)
|
|
1054
|
+
with profile_step(
|
|
1055
|
+
"search.endpoint",
|
|
1056
|
+
enabled=bool(payload.enable_reformulation),
|
|
1057
|
+
has_conversation_history=bool(payload.conversation_history),
|
|
1058
|
+
search_mode=payload.search_mode,
|
|
1059
|
+
) as endpoint_span:
|
|
1060
|
+
endpoint_span.set_data("deps_to_body_ms", deps_to_body_ms)
|
|
1061
|
+
endpoint_span.set_data(
|
|
1062
|
+
"tp_borrowed", getattr(request.state, "tp_borrowed", None)
|
|
1063
|
+
)
|
|
1064
|
+
endpoint_span.set_data("tp_total", getattr(request.state, "tp_total", None))
|
|
1065
|
+
endpoint_span.set_data("tp_waiting", getattr(request.state, "tp_waiting", None))
|
|
1066
|
+
|
|
1067
|
+
def run_search() -> Any:
|
|
1068
|
+
with profile_step("search.reflexio_cache"):
|
|
1069
|
+
reflexio = get_reflexio(org_id=org_id)
|
|
1070
|
+
return reflexio.unified_search(payload, org_id=org_id)
|
|
1071
|
+
|
|
1072
|
+
response = _run_limited_api(org_id, "search", run_search)
|
|
1073
|
+
with profile_step("search.response_view"):
|
|
1074
|
+
resp = UnifiedSearchViewResponse(
|
|
1075
|
+
success=response.success,
|
|
1076
|
+
profiles=[to_profile_view(p) for p in response.profiles],
|
|
1077
|
+
agent_playbooks=[
|
|
1078
|
+
to_agent_playbook_view(fb) for fb in response.agent_playbooks
|
|
1079
|
+
],
|
|
1080
|
+
user_playbooks=[
|
|
1081
|
+
to_user_playbook_view(rf) for rf in response.user_playbooks
|
|
1082
|
+
],
|
|
1083
|
+
reformulated_query=response.reformulated_query,
|
|
1084
|
+
msg=response.msg,
|
|
1085
|
+
agent_trace=response.agent_trace,
|
|
1086
|
+
rehydrated_text=response.rehydrated_text,
|
|
1087
|
+
)
|
|
1088
|
+
background_tasks.add_task(
|
|
1089
|
+
_meter_search_request,
|
|
1090
|
+
org_id=org_id,
|
|
1091
|
+
caller_type=caller_type,
|
|
1092
|
+
request_id=getattr(payload, "request_id", None),
|
|
1093
|
+
session_id=getattr(payload, "session_id", None),
|
|
1094
|
+
)
|
|
1095
|
+
background_tasks.add_task(
|
|
1096
|
+
_meter_applied_learnings,
|
|
1097
|
+
org_id=org_id,
|
|
1098
|
+
caller_type=caller_type,
|
|
1099
|
+
surfaced_count=len(resp.profiles)
|
|
1100
|
+
+ len(resp.agent_playbooks)
|
|
1101
|
+
+ len(resp.user_playbooks),
|
|
1102
|
+
request_id=getattr(payload, "request_id", None),
|
|
1103
|
+
session_id=getattr(payload, "session_id", None),
|
|
1104
|
+
)
|
|
1105
|
+
return resp
|
|
862
1106
|
|
|
863
1107
|
|
|
864
1108
|
@core_router.get("/api/profile_change_log", response_model=ProfileChangeLogViewResponse)
|
|
865
1109
|
def get_profile_change_log(
|
|
866
1110
|
org_id: str = Depends(default_get_org_id),
|
|
867
1111
|
) -> ProfileChangeLogViewResponse:
|
|
1112
|
+
# Serves the reconstructed profile change log (rebuilt from lineage events). The
|
|
1113
|
+
# legacy `profile_change_logs` table is no longer written; see
|
|
1114
|
+
# reconstruct_profile_change_log in lib/_profiles.py.
|
|
868
1115
|
response = get_reflexio(org_id=org_id).get_profile_change_logs()
|
|
869
1116
|
return ProfileChangeLogViewResponse(
|
|
870
1117
|
success=response.success,
|
|
@@ -1269,6 +1516,307 @@ def get_requests_endpoint(
|
|
|
1269
1516
|
)
|
|
1270
1517
|
|
|
1271
1518
|
|
|
1519
|
+
def _learning_provenance_error(
|
|
1520
|
+
payload: GetLearningProvenanceRequest,
|
|
1521
|
+
msg: str,
|
|
1522
|
+
) -> LearningProvenanceViewResponse:
|
|
1523
|
+
return LearningProvenanceViewResponse(
|
|
1524
|
+
success=False,
|
|
1525
|
+
target_kind=payload.kind,
|
|
1526
|
+
target_id=payload.id,
|
|
1527
|
+
provenance_status="unavailable",
|
|
1528
|
+
msg=msg,
|
|
1529
|
+
)
|
|
1530
|
+
|
|
1531
|
+
|
|
1532
|
+
def _parse_learning_target_int(
|
|
1533
|
+
payload: GetLearningProvenanceRequest,
|
|
1534
|
+
) -> int | LearningProvenanceViewResponse:
|
|
1535
|
+
try:
|
|
1536
|
+
return int(payload.id)
|
|
1537
|
+
except ValueError:
|
|
1538
|
+
return _learning_provenance_error(
|
|
1539
|
+
payload,
|
|
1540
|
+
f"{payload.kind} id must be an integer",
|
|
1541
|
+
)
|
|
1542
|
+
|
|
1543
|
+
|
|
1544
|
+
def _sort_interactions_by_time(interactions: list[Any]) -> list[Any]:
|
|
1545
|
+
return sorted(
|
|
1546
|
+
interactions,
|
|
1547
|
+
key=lambda i: (
|
|
1548
|
+
getattr(i, "created_at", 0) or 0,
|
|
1549
|
+
getattr(i, "interaction_id", 0),
|
|
1550
|
+
),
|
|
1551
|
+
)
|
|
1552
|
+
|
|
1553
|
+
|
|
1554
|
+
def _interaction_views(interactions: list[Any]) -> list[Any]:
|
|
1555
|
+
return [to_interaction_view(i) for i in _sort_interactions_by_time(interactions)]
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
def _effective_profile_provenance_window(
|
|
1559
|
+
reflexio: Any, trigger_source: str | None
|
|
1560
|
+
) -> tuple[int, list[str] | None]:
|
|
1561
|
+
config = reflexio.request_context.configurator.get_config()
|
|
1562
|
+
profile_config = getattr(config, "profile_extractor_config", None)
|
|
1563
|
+
if profile_config is None:
|
|
1564
|
+
return getattr(config, "window_size", DEFAULT_WINDOW_SIZE), None
|
|
1565
|
+
|
|
1566
|
+
window_size, _ = get_extractor_window_params(
|
|
1567
|
+
profile_config,
|
|
1568
|
+
getattr(config, "window_size", None),
|
|
1569
|
+
getattr(config, "stride_size", None),
|
|
1570
|
+
)
|
|
1571
|
+
should_skip, source_filter = get_effective_source_filter(
|
|
1572
|
+
profile_config,
|
|
1573
|
+
trigger_source,
|
|
1574
|
+
)
|
|
1575
|
+
if should_skip:
|
|
1576
|
+
return window_size, None
|
|
1577
|
+
return window_size, source_filter
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
def _get_profile_learning_provenance(
|
|
1581
|
+
payload: GetLearningProvenanceRequest,
|
|
1582
|
+
reflexio: Any,
|
|
1583
|
+
) -> LearningProvenanceViewResponse:
|
|
1584
|
+
storage = reflexio.request_context.storage
|
|
1585
|
+
profile = storage.get_profile_by_id(payload.id)
|
|
1586
|
+
if profile is None:
|
|
1587
|
+
return _learning_provenance_error(payload, "Profile not found")
|
|
1588
|
+
|
|
1589
|
+
if profile.source_interaction_ids:
|
|
1590
|
+
interactions = storage.get_interactions_by_ids(profile.source_interaction_ids)
|
|
1591
|
+
return LearningProvenanceViewResponse(
|
|
1592
|
+
success=True,
|
|
1593
|
+
target_kind=payload.kind,
|
|
1594
|
+
target_id=payload.id,
|
|
1595
|
+
provenance_status="exact",
|
|
1596
|
+
trigger_request_id=profile.generated_from_request_id,
|
|
1597
|
+
interactions=_interaction_views(interactions),
|
|
1598
|
+
)
|
|
1599
|
+
|
|
1600
|
+
if not profile.generated_from_request_id:
|
|
1601
|
+
return _learning_provenance_error(
|
|
1602
|
+
payload,
|
|
1603
|
+
"Profile has no generation request for provenance reconstruction",
|
|
1604
|
+
)
|
|
1605
|
+
|
|
1606
|
+
trigger_request = storage.get_request(profile.generated_from_request_id)
|
|
1607
|
+
if trigger_request is None:
|
|
1608
|
+
return _learning_provenance_error(
|
|
1609
|
+
payload,
|
|
1610
|
+
"Profile generation request was not found",
|
|
1611
|
+
)
|
|
1612
|
+
|
|
1613
|
+
trigger_interactions = storage.get_interactions_by_request_ids(
|
|
1614
|
+
[profile.generated_from_request_id]
|
|
1615
|
+
)
|
|
1616
|
+
anchor_time = (
|
|
1617
|
+
max(i.created_at for i in trigger_interactions)
|
|
1618
|
+
if trigger_interactions
|
|
1619
|
+
else trigger_request.created_at
|
|
1620
|
+
)
|
|
1621
|
+
window_size, source_filter = _effective_profile_provenance_window(
|
|
1622
|
+
reflexio,
|
|
1623
|
+
trigger_request.source,
|
|
1624
|
+
)
|
|
1625
|
+
_, interactions = storage.get_last_k_interactions_grouped(
|
|
1626
|
+
user_id=profile.user_id,
|
|
1627
|
+
k=window_size,
|
|
1628
|
+
sources=source_filter,
|
|
1629
|
+
end_time=anchor_time,
|
|
1630
|
+
)
|
|
1631
|
+
return LearningProvenanceViewResponse(
|
|
1632
|
+
success=True,
|
|
1633
|
+
target_kind=payload.kind,
|
|
1634
|
+
target_id=payload.id,
|
|
1635
|
+
provenance_status="best_effort" if interactions else "unavailable",
|
|
1636
|
+
trigger_request_id=profile.generated_from_request_id,
|
|
1637
|
+
interactions=_interaction_views(interactions),
|
|
1638
|
+
msg=None if interactions else "No interactions found for reconstructed window",
|
|
1639
|
+
)
|
|
1640
|
+
|
|
1641
|
+
|
|
1642
|
+
def _get_user_playbook_learning_provenance(
|
|
1643
|
+
payload: GetLearningProvenanceRequest,
|
|
1644
|
+
reflexio: Any,
|
|
1645
|
+
) -> LearningProvenanceViewResponse:
|
|
1646
|
+
parsed_id = _parse_learning_target_int(payload)
|
|
1647
|
+
if isinstance(parsed_id, LearningProvenanceViewResponse):
|
|
1648
|
+
return parsed_id
|
|
1649
|
+
|
|
1650
|
+
storage = reflexio.request_context.storage
|
|
1651
|
+
playbook = storage.get_user_playbook_by_id(parsed_id)
|
|
1652
|
+
if playbook is None:
|
|
1653
|
+
return _learning_provenance_error(payload, "User playbook not found")
|
|
1654
|
+
|
|
1655
|
+
status = "exact"
|
|
1656
|
+
if playbook.source_interaction_ids:
|
|
1657
|
+
interactions = storage.get_interactions_by_ids(playbook.source_interaction_ids)
|
|
1658
|
+
elif playbook.request_id:
|
|
1659
|
+
status = "best_effort"
|
|
1660
|
+
interactions = storage.get_interactions_by_request_ids([playbook.request_id])
|
|
1661
|
+
else:
|
|
1662
|
+
status = "unavailable"
|
|
1663
|
+
interactions = []
|
|
1664
|
+
|
|
1665
|
+
return LearningProvenanceViewResponse(
|
|
1666
|
+
success=True,
|
|
1667
|
+
target_kind=payload.kind,
|
|
1668
|
+
target_id=payload.id,
|
|
1669
|
+
provenance_status=status if interactions else "unavailable",
|
|
1670
|
+
trigger_request_id=playbook.request_id,
|
|
1671
|
+
interactions=_interaction_views(interactions),
|
|
1672
|
+
msg=None if interactions else "No interactions found for this user playbook",
|
|
1673
|
+
)
|
|
1674
|
+
|
|
1675
|
+
|
|
1676
|
+
def _get_agent_playbook_learning_provenance(
|
|
1677
|
+
payload: GetLearningProvenanceRequest,
|
|
1678
|
+
reflexio: Any,
|
|
1679
|
+
) -> LearningProvenanceViewResponse:
|
|
1680
|
+
parsed_id = _parse_learning_target_int(payload)
|
|
1681
|
+
if isinstance(parsed_id, LearningProvenanceViewResponse):
|
|
1682
|
+
return parsed_id
|
|
1683
|
+
|
|
1684
|
+
storage = reflexio.request_context.storage
|
|
1685
|
+
agent_playbook = storage.get_agent_playbook_by_id(parsed_id)
|
|
1686
|
+
if agent_playbook is None:
|
|
1687
|
+
return _learning_provenance_error(payload, "Agent playbook not found")
|
|
1688
|
+
|
|
1689
|
+
windows = storage.get_source_windows_for_agent_playbook(parsed_id)
|
|
1690
|
+
if not windows:
|
|
1691
|
+
return LearningProvenanceViewResponse(
|
|
1692
|
+
success=True,
|
|
1693
|
+
target_kind=payload.kind,
|
|
1694
|
+
target_id=payload.id,
|
|
1695
|
+
provenance_status="unavailable",
|
|
1696
|
+
msg="No source user playbooks are recorded for this agent playbook",
|
|
1697
|
+
)
|
|
1698
|
+
|
|
1699
|
+
user_playbook_ids = [window.user_playbook_id for window in windows]
|
|
1700
|
+
user_playbooks = storage.get_user_playbooks_by_ids_any_user(
|
|
1701
|
+
user_playbook_ids,
|
|
1702
|
+
status_filter=None,
|
|
1703
|
+
)
|
|
1704
|
+
user_playbooks_by_id = {
|
|
1705
|
+
playbook.user_playbook_id: playbook for playbook in user_playbooks
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
interaction_ids_to_fetch: set[int] = set()
|
|
1709
|
+
request_ids_to_fetch: set[str] = set()
|
|
1710
|
+
group_sources: dict[int, tuple[list[int], str | None, bool]] = {}
|
|
1711
|
+
for window in windows:
|
|
1712
|
+
source_user_playbook = user_playbooks_by_id.get(window.user_playbook_id)
|
|
1713
|
+
if source_user_playbook is None:
|
|
1714
|
+
continue
|
|
1715
|
+
|
|
1716
|
+
source_ids = list(window.source_interaction_ids)
|
|
1717
|
+
request_id: str | None = None
|
|
1718
|
+
uses_fallback = False
|
|
1719
|
+
if not source_ids and source_user_playbook.source_interaction_ids:
|
|
1720
|
+
source_ids = list(source_user_playbook.source_interaction_ids)
|
|
1721
|
+
elif not source_ids and source_user_playbook.request_id:
|
|
1722
|
+
request_id = source_user_playbook.request_id
|
|
1723
|
+
uses_fallback = True
|
|
1724
|
+
elif not source_ids:
|
|
1725
|
+
uses_fallback = True
|
|
1726
|
+
|
|
1727
|
+
interaction_ids_to_fetch.update(source_ids)
|
|
1728
|
+
if request_id:
|
|
1729
|
+
request_ids_to_fetch.add(request_id)
|
|
1730
|
+
group_sources[window.user_playbook_id] = (source_ids, request_id, uses_fallback)
|
|
1731
|
+
|
|
1732
|
+
interactions_by_id = (
|
|
1733
|
+
{
|
|
1734
|
+
interaction.interaction_id: interaction
|
|
1735
|
+
for interaction in storage.get_interactions_by_ids(
|
|
1736
|
+
sorted(interaction_ids_to_fetch)
|
|
1737
|
+
)
|
|
1738
|
+
}
|
|
1739
|
+
if interaction_ids_to_fetch
|
|
1740
|
+
else {}
|
|
1741
|
+
)
|
|
1742
|
+
request_interactions: dict[str, list[Any]] = {}
|
|
1743
|
+
if request_ids_to_fetch:
|
|
1744
|
+
for interaction in storage.get_interactions_by_request_ids(
|
|
1745
|
+
sorted(request_ids_to_fetch)
|
|
1746
|
+
):
|
|
1747
|
+
request_interactions.setdefault(interaction.request_id, []).append(
|
|
1748
|
+
interaction
|
|
1749
|
+
)
|
|
1750
|
+
|
|
1751
|
+
groups: list[SourceUserPlaybookProvenanceView] = []
|
|
1752
|
+
used_fallback = False
|
|
1753
|
+
found_interactions = False
|
|
1754
|
+
for window in windows:
|
|
1755
|
+
source_user_playbook = user_playbooks_by_id.get(window.user_playbook_id)
|
|
1756
|
+
if source_user_playbook is None:
|
|
1757
|
+
used_fallback = True
|
|
1758
|
+
continue
|
|
1759
|
+
|
|
1760
|
+
source_ids, request_id, uses_fallback = group_sources.get(
|
|
1761
|
+
window.user_playbook_id,
|
|
1762
|
+
([], None, True),
|
|
1763
|
+
)
|
|
1764
|
+
used_fallback = used_fallback or uses_fallback
|
|
1765
|
+
interactions = (
|
|
1766
|
+
[interactions_by_id[i] for i in source_ids if i in interactions_by_id]
|
|
1767
|
+
if source_ids
|
|
1768
|
+
else request_interactions.get(request_id or "", [])
|
|
1769
|
+
)
|
|
1770
|
+
|
|
1771
|
+
found_interactions = found_interactions or bool(interactions)
|
|
1772
|
+
groups.append(
|
|
1773
|
+
SourceUserPlaybookProvenanceView(
|
|
1774
|
+
user_playbook=to_user_playbook_view(source_user_playbook),
|
|
1775
|
+
interactions=_interaction_views(interactions),
|
|
1776
|
+
source_interaction_ids=source_ids,
|
|
1777
|
+
)
|
|
1778
|
+
)
|
|
1779
|
+
|
|
1780
|
+
if not groups:
|
|
1781
|
+
return _learning_provenance_error(
|
|
1782
|
+
payload,
|
|
1783
|
+
"Recorded source user playbooks were not found",
|
|
1784
|
+
)
|
|
1785
|
+
|
|
1786
|
+
return LearningProvenanceViewResponse(
|
|
1787
|
+
success=True,
|
|
1788
|
+
target_kind=payload.kind,
|
|
1789
|
+
target_id=payload.id,
|
|
1790
|
+
provenance_status=(
|
|
1791
|
+
"exact"
|
|
1792
|
+
if found_interactions and not used_fallback
|
|
1793
|
+
else "best_effort"
|
|
1794
|
+
if found_interactions
|
|
1795
|
+
else "unavailable"
|
|
1796
|
+
),
|
|
1797
|
+
source_user_playbooks=groups,
|
|
1798
|
+
msg=None if found_interactions else "No source interactions were found",
|
|
1799
|
+
)
|
|
1800
|
+
|
|
1801
|
+
|
|
1802
|
+
@core_router.post(
|
|
1803
|
+
"/api/get_learning_provenance",
|
|
1804
|
+
response_model=LearningProvenanceViewResponse,
|
|
1805
|
+
response_model_exclude_none=True,
|
|
1806
|
+
)
|
|
1807
|
+
def get_learning_provenance(
|
|
1808
|
+
payload: GetLearningProvenanceRequest,
|
|
1809
|
+
org_id: str = Depends(default_get_org_id),
|
|
1810
|
+
) -> LearningProvenanceViewResponse:
|
|
1811
|
+
"""Return read-only learning provenance for a generated learning row."""
|
|
1812
|
+
reflexio = get_reflexio(org_id=org_id)
|
|
1813
|
+
if payload.kind == "profile":
|
|
1814
|
+
return _get_profile_learning_provenance(payload, reflexio)
|
|
1815
|
+
if payload.kind == "user_playbook":
|
|
1816
|
+
return _get_user_playbook_learning_provenance(payload, reflexio)
|
|
1817
|
+
return _get_agent_playbook_learning_provenance(payload, reflexio)
|
|
1818
|
+
|
|
1819
|
+
|
|
1272
1820
|
@core_router.post(
|
|
1273
1821
|
"/api/get_profiles",
|
|
1274
1822
|
response_model=GetProfilesViewResponse,
|
|
@@ -1296,6 +1844,8 @@ def get_all_profiles(
|
|
|
1296
1844
|
request: Request,
|
|
1297
1845
|
limit: int = 100,
|
|
1298
1846
|
status_filter: str | None = None,
|
|
1847
|
+
profile_id: str | None = None,
|
|
1848
|
+
include_tombstones: bool = False,
|
|
1299
1849
|
org_id: str = Depends(default_get_org_id),
|
|
1300
1850
|
) -> GetProfilesViewResponse:
|
|
1301
1851
|
"""Get all user profiles across all users.
|
|
@@ -1303,6 +1853,9 @@ def get_all_profiles(
|
|
|
1303
1853
|
Args:
|
|
1304
1854
|
limit (int, optional): Maximum number of profiles to return. Defaults to 100.
|
|
1305
1855
|
status_filter (str, optional): Filter by profile status. Can be "current", "pending", or "archived".
|
|
1856
|
+
profile_id (str, optional): Exact profile ID to retrieve.
|
|
1857
|
+
include_tombstones (bool, optional): Include merged/superseded rows when
|
|
1858
|
+
looking up a specific profile_id.
|
|
1306
1859
|
org_id (str): Organization ID
|
|
1307
1860
|
|
|
1308
1861
|
Returns:
|
|
@@ -1310,6 +1863,24 @@ def get_all_profiles(
|
|
|
1310
1863
|
"""
|
|
1311
1864
|
reflexio = get_reflexio(org_id=org_id)
|
|
1312
1865
|
|
|
1866
|
+
if profile_id:
|
|
1867
|
+
storage = reflexio.request_context.storage
|
|
1868
|
+
if storage is None:
|
|
1869
|
+
return GetProfilesViewResponse(
|
|
1870
|
+
success=True,
|
|
1871
|
+
user_profiles=[],
|
|
1872
|
+
msg="Storage is not configured",
|
|
1873
|
+
)
|
|
1874
|
+
profile = storage.get_profile_by_id(
|
|
1875
|
+
profile_id, include_tombstones=include_tombstones
|
|
1876
|
+
)
|
|
1877
|
+
profiles = [profile] if profile else []
|
|
1878
|
+
return GetProfilesViewResponse(
|
|
1879
|
+
success=True,
|
|
1880
|
+
user_profiles=[to_profile_view(p) for p in profiles],
|
|
1881
|
+
msg=f"Found {len(profiles)} profile(s)",
|
|
1882
|
+
)
|
|
1883
|
+
|
|
1313
1884
|
# Map status_filter string to Status list
|
|
1314
1885
|
status_filter_list = None
|
|
1315
1886
|
if status_filter == "current":
|
|
@@ -1378,17 +1949,30 @@ def set_config(
|
|
|
1378
1949
|
"""Set configuration for the organization.
|
|
1379
1950
|
|
|
1380
1951
|
Args:
|
|
1381
|
-
config (
|
|
1952
|
+
config (dict[str, Any]): The configuration payload to set
|
|
1382
1953
|
org_id (str): Organization ID
|
|
1383
1954
|
|
|
1384
1955
|
Returns:
|
|
1385
1956
|
dict: Response containing success status and message
|
|
1386
1957
|
"""
|
|
1958
|
+
from pydantic import ValidationError
|
|
1959
|
+
|
|
1387
1960
|
# Create Reflexio instance to access the configurator through request_context
|
|
1388
1961
|
reflexio = get_reflexio(org_id=org_id)
|
|
1962
|
+
configurator = reflexio.request_context.configurator
|
|
1963
|
+
normalized_config = configurator.normalize_config_payload(config)
|
|
1964
|
+
if not isinstance(normalized_config, dict):
|
|
1965
|
+
normalized_config = config
|
|
1966
|
+
try:
|
|
1967
|
+
Config.model_validate(normalized_config)
|
|
1968
|
+
except ValidationError as exc:
|
|
1969
|
+
raise HTTPException(
|
|
1970
|
+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
1971
|
+
detail=exc.errors(),
|
|
1972
|
+
) from exc
|
|
1389
1973
|
|
|
1390
1974
|
# Set the config using Reflexio's set_config method
|
|
1391
|
-
response = reflexio.set_config(
|
|
1975
|
+
response = reflexio.set_config(normalized_config)
|
|
1392
1976
|
|
|
1393
1977
|
# Invalidate cache on successful config change to ensure fresh instance next request
|
|
1394
1978
|
if response.success:
|
|
@@ -1568,26 +2152,40 @@ def get_user_playbooks(
|
|
|
1568
2152
|
response_model=GetAgentPlaybooksViewResponse,
|
|
1569
2153
|
response_model_exclude_none=True,
|
|
1570
2154
|
)
|
|
2155
|
+
@limiter.limit("120/minute") # Rate limit for read operations
|
|
1571
2156
|
def get_agent_playbooks(
|
|
1572
|
-
request:
|
|
2157
|
+
request: Request,
|
|
2158
|
+
payload: GetAgentPlaybooksRequest,
|
|
1573
2159
|
org_id: str = Depends(default_get_org_id),
|
|
2160
|
+
caller_type: str = Depends(default_get_caller_type),
|
|
2161
|
+
_gate: None = Depends(default_billing_gate("application")), # noqa: B008
|
|
1574
2162
|
) -> GetAgentPlaybooksViewResponse:
|
|
1575
2163
|
"""Get agent playbooks with internal fields filtered out.
|
|
1576
2164
|
|
|
1577
2165
|
Args:
|
|
1578
|
-
request (
|
|
2166
|
+
request (Request): The HTTP request object (for rate limiting)
|
|
2167
|
+
payload (GetAgentPlaybooksRequest): The get request
|
|
1579
2168
|
org_id (str): Organization ID
|
|
2169
|
+
caller_type (str): Billing caller classification (injected via dependency).
|
|
1580
2170
|
|
|
1581
2171
|
Returns:
|
|
1582
2172
|
GetAgentPlaybooksViewResponse: Response containing agent playbooks without internal fields
|
|
1583
2173
|
"""
|
|
1584
2174
|
reflexio = get_reflexio(org_id=org_id)
|
|
1585
|
-
response = reflexio.get_agent_playbooks(
|
|
1586
|
-
|
|
2175
|
+
response = reflexio.get_agent_playbooks(payload)
|
|
2176
|
+
resp = GetAgentPlaybooksViewResponse(
|
|
1587
2177
|
success=response.success,
|
|
1588
2178
|
agent_playbooks=[to_agent_playbook_view(fb) for fb in response.agent_playbooks],
|
|
1589
2179
|
msg=response.msg,
|
|
1590
2180
|
)
|
|
2181
|
+
_meter_applied_learnings(
|
|
2182
|
+
org_id=org_id,
|
|
2183
|
+
caller_type=caller_type,
|
|
2184
|
+
surfaced_count=len(resp.agent_playbooks),
|
|
2185
|
+
request_id=getattr(payload, "request_id", None),
|
|
2186
|
+
session_id=getattr(payload, "session_id", None),
|
|
2187
|
+
)
|
|
2188
|
+
return resp
|
|
1591
2189
|
|
|
1592
2190
|
|
|
1593
2191
|
@core_router.post(
|
|
@@ -2094,10 +2692,8 @@ def _read_grade_on_demand_cache(
|
|
|
2094
2692
|
def _resolve_session_user_id(storage: Any, session_id: str) -> str | None:
|
|
2095
2693
|
"""Look up the user_id that owns a session_id without requiring it as input.
|
|
2096
2694
|
|
|
2097
|
-
Uses
|
|
2098
|
-
|
|
2099
|
-
request-fetcher requires a user_id paired with it. Returns None when
|
|
2100
|
-
the session has no requests, signalling NO_REQUESTS to the caller.
|
|
2695
|
+
Uses the first-request bulk helper even for this single-session path so the
|
|
2696
|
+
lookup can use the same indexed query shape as evaluation overview.
|
|
2101
2697
|
|
|
2102
2698
|
Args:
|
|
2103
2699
|
storage: The request's storage backend.
|
|
@@ -2107,17 +2703,17 @@ def _resolve_session_user_id(storage: Any, session_id: str) -> str | None:
|
|
|
2107
2703
|
str | None: The user_id of the earliest request in the session,
|
|
2108
2704
|
or None when no requests exist.
|
|
2109
2705
|
"""
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
if
|
|
2706
|
+
first_requests = storage.get_first_requests_by_session_ids([session_id])
|
|
2707
|
+
first = first_requests.get(session_id)
|
|
2708
|
+
if first is None:
|
|
2113
2709
|
return None
|
|
2114
|
-
|
|
2115
|
-
return earliest.request.user_id
|
|
2710
|
+
return first.user_id
|
|
2116
2711
|
|
|
2117
2712
|
|
|
2118
2713
|
def _find_fresh_result_id(
|
|
2119
2714
|
storage: Any,
|
|
2120
2715
|
*,
|
|
2716
|
+
user_id: str,
|
|
2121
2717
|
session_id: str,
|
|
2122
2718
|
agent_version: str,
|
|
2123
2719
|
evaluation_name: str,
|
|
@@ -2125,12 +2721,12 @@ def _find_fresh_result_id(
|
|
|
2125
2721
|
) -> int | None:
|
|
2126
2722
|
"""Locate the result_id written by the most-recent grade for this session.
|
|
2127
2723
|
|
|
2128
|
-
The runner writes rows but doesn't return the id.
|
|
2129
|
-
|
|
2130
|
-
the regen worker's prior-row capture (group_evaluation_runner step 5).
|
|
2724
|
+
The runner writes rows but doesn't return the id. Use the targeted result-id
|
|
2725
|
+
lookup so this path does not scan broad evaluation windows.
|
|
2131
2726
|
|
|
2132
2727
|
Args:
|
|
2133
2728
|
storage: The request's storage backend.
|
|
2729
|
+
user_id (str): The user whose session slice was graded.
|
|
2134
2730
|
session_id (str): The graded session.
|
|
2135
2731
|
agent_version (str): The version dimension.
|
|
2136
2732
|
evaluation_name (str): Evaluator/result namespace to isolate readback.
|
|
@@ -2140,20 +2736,16 @@ def _find_fresh_result_id(
|
|
|
2140
2736
|
int | None: result_id of the latest matching row, or None if the
|
|
2141
2737
|
runner wrote nothing.
|
|
2142
2738
|
"""
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
and r.result_id not in previous_result_ids
|
|
2152
|
-
]
|
|
2153
|
-
if not matched:
|
|
2739
|
+
result_ids = storage.get_agent_success_evaluation_result_ids(
|
|
2740
|
+
user_id=user_id,
|
|
2741
|
+
session_id=session_id,
|
|
2742
|
+
evaluation_name=evaluation_name,
|
|
2743
|
+
agent_version=agent_version,
|
|
2744
|
+
)
|
|
2745
|
+
fresh_result_ids = [rid for rid in result_ids if rid not in previous_result_ids]
|
|
2746
|
+
if not fresh_result_ids:
|
|
2154
2747
|
return None
|
|
2155
|
-
|
|
2156
|
-
return latest.result_id
|
|
2748
|
+
return max(fresh_result_ids)
|
|
2157
2749
|
|
|
2158
2750
|
|
|
2159
2751
|
@core_router.post(
|
|
@@ -2224,13 +2816,14 @@ def grade_on_demand(
|
|
|
2224
2816
|
skipped_reason="NO_REQUESTS",
|
|
2225
2817
|
)
|
|
2226
2818
|
|
|
2227
|
-
previous_result_ids =
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2819
|
+
previous_result_ids = set(
|
|
2820
|
+
storage.get_agent_success_evaluation_result_ids(
|
|
2821
|
+
user_id=user_id,
|
|
2822
|
+
session_id=payload.session_id,
|
|
2823
|
+
evaluation_name=evaluation_name,
|
|
2824
|
+
agent_version=payload.agent_version,
|
|
2231
2825
|
)
|
|
2232
|
-
|
|
2233
|
-
}
|
|
2826
|
+
)
|
|
2234
2827
|
|
|
2235
2828
|
# Two operation_state rows are intentionally written for this session:
|
|
2236
2829
|
# 1) `grade_on_demand::{org_id}::{session_id}::{agent_version}::{evaluation_name}`
|
|
@@ -2255,6 +2848,7 @@ def grade_on_demand(
|
|
|
2255
2848
|
|
|
2256
2849
|
result_id = _find_fresh_result_id(
|
|
2257
2850
|
storage,
|
|
2851
|
+
user_id=user_id,
|
|
2258
2852
|
session_id=payload.session_id,
|
|
2259
2853
|
agent_version=payload.agent_version,
|
|
2260
2854
|
evaluation_name=evaluation_name,
|
|
@@ -2307,8 +2901,8 @@ def get_recent_shadow_comparisons(
|
|
|
2307
2901
|
Filters to the org's currently pinned
|
|
2308
2902
|
``Config.shadow_comparison_judge_prompt_version`` so verdicts produced
|
|
2309
2903
|
under an older rubric do not mix into the drawer or the Top 10
|
|
2310
|
-
disagreements widget. Storage returns verdicts
|
|
2311
|
-
|
|
2904
|
+
disagreements widget. Storage returns verdicts newest-first and caps the
|
|
2905
|
+
read at ``limit``.
|
|
2312
2906
|
|
|
2313
2907
|
Args:
|
|
2314
2908
|
limit (int): Max verdicts to return. Clamped to ``[1, 100]``.
|
|
@@ -2340,10 +2934,11 @@ def get_recent_shadow_comparisons(
|
|
|
2340
2934
|
|
|
2341
2935
|
now = int(datetime.now(UTC).timestamp())
|
|
2342
2936
|
try:
|
|
2343
|
-
verdicts = storage.
|
|
2937
|
+
verdicts = storage.get_recent_shadow_comparison_verdicts(
|
|
2344
2938
|
from_ts=now - _RECENT_SHADOW_COMPARISONS_LOOKBACK_SECONDS,
|
|
2345
2939
|
to_ts=now,
|
|
2346
2940
|
judge_prompt_version=pinned_version,
|
|
2941
|
+
limit=clamped_limit,
|
|
2347
2942
|
)
|
|
2348
2943
|
except NotImplementedError:
|
|
2349
2944
|
# Backends that don't support shadow verdicts (e.g., Disk) should
|
|
@@ -2351,9 +2946,7 @@ def get_recent_shadow_comparisons(
|
|
|
2351
2946
|
# "no data yet" in the UI.
|
|
2352
2947
|
return GetRecentShadowComparisonsResponse(verdicts=[])
|
|
2353
2948
|
|
|
2354
|
-
|
|
2355
|
-
newest_first = list(reversed(verdicts))[:clamped_limit]
|
|
2356
|
-
return GetRecentShadowComparisonsResponse(verdicts=newest_first)
|
|
2949
|
+
return GetRecentShadowComparisonsResponse(verdicts=verdicts)
|
|
2357
2950
|
|
|
2358
2951
|
|
|
2359
2952
|
@core_router.post(
|
|
@@ -2714,6 +3307,9 @@ def create_app(
|
|
|
2714
3307
|
additional_routers: list[APIRouter] | None = None,
|
|
2715
3308
|
middleware_config: dict | None = None,
|
|
2716
3309
|
require_auth: bool = False,
|
|
3310
|
+
get_caller_type: Callable[..., str] | None = None,
|
|
3311
|
+
get_billing_gate: Callable[[str], Callable[..., None]] | None = None,
|
|
3312
|
+
mount_data_plane: bool = True,
|
|
2717
3313
|
) -> FastAPI:
|
|
2718
3314
|
"""Factory to create a FastAPI app.
|
|
2719
3315
|
|
|
@@ -2724,6 +3320,23 @@ def create_app(
|
|
|
2724
3320
|
middleware_config: Optional middleware overrides (not used yet, reserved for future).
|
|
2725
3321
|
require_auth: When True, declares a Bearer security scheme in the OpenAPI spec
|
|
2726
3322
|
so Swagger UI shows lock icons and the Authorize button works.
|
|
3323
|
+
get_caller_type: Custom dependency for classifying the caller (e.g., production
|
|
3324
|
+
agent vs dashboard). When provided, overrides the default_get_caller_type
|
|
3325
|
+
dependency globally, exactly mirroring the get_org_id override.
|
|
3326
|
+
get_billing_gate: Optional factory ``(line: str) -> FastAPI dependency`` that
|
|
3327
|
+
replaces the default no-op gate for each billable billing line. When
|
|
3328
|
+
provided, for every line used in the app (``"application"`` and
|
|
3329
|
+
``"learnings_generated"``) the returned dependency overrides the
|
|
3330
|
+
``default_billing_gate(line)`` sentinel in ``dependency_overrides``,
|
|
3331
|
+
exactly mirroring the ``get_caller_type`` override pattern.
|
|
3332
|
+
mount_data_plane: When True (default), include the data-plane routers
|
|
3333
|
+
(core, stall-state, pending-tool-call) and run the data-plane
|
|
3334
|
+
lifespan work (LLM availability check, cross-encoder prewarm,
|
|
3335
|
+
resume scheduler). When False, skip both so a control-plane host
|
|
3336
|
+
can build an app without requiring LLM/storage or starting the
|
|
3337
|
+
scheduler, while keeping all other scaffolding (middleware, CORS,
|
|
3338
|
+
auth overrides, OpenAPI security, health, ``/meta/version``,
|
|
3339
|
+
``additional_routers``).
|
|
2727
3340
|
|
|
2728
3341
|
Returns:
|
|
2729
3342
|
Configured FastAPI application.
|
|
@@ -2731,12 +3344,19 @@ def create_app(
|
|
|
2731
3344
|
from collections.abc import AsyncIterator
|
|
2732
3345
|
from contextlib import asynccontextmanager
|
|
2733
3346
|
|
|
2734
|
-
from reflexio.server._auth import
|
|
3347
|
+
from reflexio.server._auth import (
|
|
3348
|
+
default_billing_gate,
|
|
3349
|
+
default_get_caller_type,
|
|
3350
|
+
default_get_org_id,
|
|
3351
|
+
)
|
|
2735
3352
|
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
2736
3353
|
from reflexio.server.llm.model_defaults import validate_llm_availability
|
|
2737
3354
|
from reflexio.server.services.extraction.resume_scheduler import (
|
|
2738
3355
|
maybe_start_resume_scheduler,
|
|
2739
3356
|
)
|
|
3357
|
+
from reflexio.server.services.lineage.gc_scheduler import (
|
|
3358
|
+
maybe_start_lineage_gc,
|
|
3359
|
+
)
|
|
2740
3360
|
|
|
2741
3361
|
def _lifespan_org_id() -> str:
|
|
2742
3362
|
if get_org_id is None:
|
|
@@ -2755,23 +3375,33 @@ def create_app(
|
|
|
2755
3375
|
|
|
2756
3376
|
@asynccontextmanager
|
|
2757
3377
|
async def lifespan(app: FastAPI) -> AsyncIterator[None]: # noqa: ARG001
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
3378
|
+
scheduler = None
|
|
3379
|
+
gc_scheduler = None
|
|
3380
|
+
if mount_data_plane:
|
|
3381
|
+
log_publish_hardware_capacity()
|
|
3382
|
+
validate_llm_availability()
|
|
3383
|
+
from reflexio.server.llm.rerank import prewarm as _prewarm_cross_encoder
|
|
3384
|
+
|
|
3385
|
+
_prewarm_cross_encoder()
|
|
3386
|
+
# The scheduler discovers every org with resumable work each tick and
|
|
3387
|
+
# drives a per-org worker with org-scoped claims, so it is not limited
|
|
3388
|
+
# to the bootstrap org. The bootstrap org is only used to read config
|
|
3389
|
+
# and to seed cross-org discovery.
|
|
3390
|
+
scheduler = maybe_start_resume_scheduler(
|
|
3391
|
+
lambda org_id: RequestContext(org_id=org_id),
|
|
3392
|
+
bootstrap_org_id=_lifespan_org_id(),
|
|
3393
|
+
)
|
|
3394
|
+
gc_scheduler = maybe_start_lineage_gc(
|
|
3395
|
+
lambda org_id: RequestContext(org_id=org_id),
|
|
3396
|
+
bootstrap_org_id=_lifespan_org_id(),
|
|
3397
|
+
)
|
|
2770
3398
|
try:
|
|
2771
3399
|
yield
|
|
2772
3400
|
finally:
|
|
2773
3401
|
if scheduler is not None:
|
|
2774
3402
|
scheduler.stop()
|
|
3403
|
+
if gc_scheduler is not None:
|
|
3404
|
+
gc_scheduler.stop()
|
|
2775
3405
|
|
|
2776
3406
|
app = FastAPI(docs_url="/docs", lifespan=lifespan)
|
|
2777
3407
|
|
|
@@ -2837,6 +3467,21 @@ def create_app(
|
|
|
2837
3467
|
if get_org_id is not None:
|
|
2838
3468
|
app.dependency_overrides[default_get_org_id] = get_org_id
|
|
2839
3469
|
|
|
3470
|
+
# Override get_caller_type dependency if custom one provided
|
|
3471
|
+
if get_caller_type is not None:
|
|
3472
|
+
app.dependency_overrides[default_get_caller_type] = get_caller_type
|
|
3473
|
+
|
|
3474
|
+
# Override billing gate dependencies if a custom gate factory is provided.
|
|
3475
|
+
# Each billing line needs its own override because dependency_overrides is
|
|
3476
|
+
# keyed by callable identity. ``default_billing_gate`` uses lru_cache so
|
|
3477
|
+
# the same sentinel object is returned for the same line — which is why
|
|
3478
|
+
# the overrides reliably fire at request time.
|
|
3479
|
+
if get_billing_gate is not None:
|
|
3480
|
+
for line in ("application", "learnings_generated"):
|
|
3481
|
+
app.dependency_overrides[default_billing_gate(line)] = get_billing_gate(
|
|
3482
|
+
line
|
|
3483
|
+
)
|
|
3484
|
+
|
|
2840
3485
|
# When a custom get_org_id is provided together with require_auth,
|
|
2841
3486
|
# auth is enforced on every route — mark this app instance so the
|
|
2842
3487
|
# token-gated my_config endpoint becomes reachable. Using
|
|
@@ -2845,14 +3490,18 @@ def create_app(
|
|
|
2845
3490
|
# multi-tenant embeddings) can coexist without leaking state.
|
|
2846
3491
|
app.state.my_config_enabled = bool(get_org_id is not None and require_auth)
|
|
2847
3492
|
|
|
2848
|
-
# Include core
|
|
2849
|
-
|
|
3493
|
+
# Include data-plane routes (core, stall-state, pending-tool-call). A
|
|
3494
|
+
# control-plane host sets mount_data_plane=False to skip these while
|
|
3495
|
+
# keeping every other piece of scaffolding below.
|
|
3496
|
+
if mount_data_plane:
|
|
3497
|
+
# Include core routes
|
|
3498
|
+
app.include_router(core_router)
|
|
2850
3499
|
|
|
2851
|
-
|
|
2852
|
-
|
|
3500
|
+
# Include stall_state routes
|
|
3501
|
+
app.include_router(stall_state_api.router)
|
|
2853
3502
|
|
|
2854
|
-
|
|
2855
|
-
|
|
3503
|
+
# Include pending tool call routes
|
|
3504
|
+
app.include_router(pending_tool_call_api.router)
|
|
2856
3505
|
|
|
2857
3506
|
# Include additional routers
|
|
2858
3507
|
for router in additional_routers or []:
|