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.
Files changed (323) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +34 -5
  3. package/bin/claude-smart.js +295 -5
  4. package/package.json +22 -3
  5. package/plugin/.claude-plugin/plugin.json +4 -2
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +22 -2
  8. package/plugin/commands/clear-all.md +1 -0
  9. package/plugin/commands/dashboard.md +1 -0
  10. package/plugin/commands/learn.md +1 -0
  11. package/plugin/commands/restart.md +1 -0
  12. package/plugin/commands/show.md +1 -0
  13. package/plugin/dashboard/app/configure/env/page.tsx +40 -14
  14. package/plugin/dashboard/app/configure/server/page.tsx +51 -1
  15. package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
  16. package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
  17. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
  18. package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
  19. package/plugin/dashboard/lib/reflexio-client.ts +16 -0
  20. package/plugin/dashboard/lib/status.ts +10 -3
  21. package/plugin/dashboard/lib/types.ts +18 -6
  22. package/plugin/hooks/codex-hooks.json +0 -1
  23. package/plugin/opencode/assistant-buffer.ts +108 -0
  24. package/plugin/opencode/dist/assistant-buffer.js +96 -0
  25. package/plugin/opencode/dist/internal.js +12 -0
  26. package/plugin/opencode/dist/payload.js +85 -0
  27. package/plugin/opencode/dist/server.mjs +158 -0
  28. package/plugin/opencode/internal.ts +18 -0
  29. package/plugin/opencode/package.json +3 -0
  30. package/plugin/opencode/payload.ts +90 -0
  31. package/plugin/opencode/server.mts +174 -0
  32. package/plugin/opencode/tsconfig.json +13 -0
  33. package/plugin/pyproject.toml +20 -3
  34. package/plugin/scripts/_lib.sh +77 -29
  35. package/plugin/scripts/backend-service.sh +27 -5
  36. package/plugin/scripts/dashboard-service.sh +7 -5
  37. package/plugin/scripts/ensure-plugin-root.sh +4 -4
  38. package/plugin/scripts/hook_entry.sh +3 -3
  39. package/plugin/scripts/opencode-claude-compat +4 -0
  40. package/plugin/scripts/opencode-claude-compat.cmd +3 -0
  41. package/plugin/scripts/opencode-claude-compat.js +225 -0
  42. package/plugin/scripts/smart-install.sh +10 -8
  43. package/plugin/src/README.md +1 -1
  44. package/plugin/src/claude_smart/cli.py +304 -6
  45. package/plugin/src/claude_smart/env_config.py +12 -4
  46. package/plugin/src/claude_smart/events/session_start.py +26 -7
  47. package/plugin/src/claude_smart/events/stop.py +1 -1
  48. package/plugin/src/claude_smart/ids.py +1 -1
  49. package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
  50. package/plugin/src/claude_smart/runtime.py +7 -1
  51. package/plugin/uv.lock +1 -1
  52. package/plugin/vendor/reflexio/.env.example +54 -30
  53. package/plugin/vendor/reflexio/README.md +14 -8
  54. package/plugin/vendor/reflexio/pyproject.toml +13 -1
  55. package/plugin/vendor/reflexio/reflexio/README.md +1 -0
  56. package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
  57. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
  58. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
  59. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
  60. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +210 -50
  63. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
  64. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
  65. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
  66. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
  67. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
  68. package/plugin/vendor/reflexio/reflexio/client/client.py +57 -13
  69. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
  70. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
  71. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
  72. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
  142. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +238 -10
  143. package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
  144. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +16 -5
  145. package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
  146. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +198 -6
  147. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
  148. package/plugin/vendor/reflexio/reflexio/lib/_search.py +29 -20
  149. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +2 -1
  150. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
  151. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
  152. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
  153. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
  154. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
  155. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +52 -0
  156. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
  157. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
  158. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +88 -7
  159. package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
  160. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
  161. package/plugin/vendor/reflexio/reflexio/server/README.md +45 -32
  162. package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
  163. package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
  164. package/plugin/vendor/reflexio/reflexio/server/_auth.py +65 -2
  165. package/plugin/vendor/reflexio/reflexio/server/api.py +734 -85
  166. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
  167. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +3 -1
  168. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
  169. package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
  170. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
  171. package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
  172. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
  173. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
  174. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
  175. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
  176. package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
  177. package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
  178. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
  179. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
  180. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
  181. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
  182. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
  183. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
  184. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
  185. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
  186. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
  187. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
  188. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +266 -0
  189. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
  190. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
  191. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
  192. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
  193. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
  194. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
  195. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
  196. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
  197. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
  198. package/plugin/vendor/reflexio/reflexio/server/services/README.md +26 -11
  199. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
  200. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
  201. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
  202. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
  203. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
  204. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
  205. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
  206. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
  207. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
  208. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
  209. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
  210. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
  211. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
  212. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
  213. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
  214. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
  215. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
  216. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
  217. package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
  218. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
  219. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
  220. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
  221. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +39 -16
  222. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
  223. package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
  224. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +193 -0
  225. package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
  226. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
  227. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +11 -12
  228. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
  229. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +316 -178
  230. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
  231. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
  232. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
  233. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
  234. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
  235. package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +84 -0
  236. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
  237. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
  238. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
  239. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
  240. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
  241. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
  242. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +30 -22
  243. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
  244. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
  245. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
  246. package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
  247. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
  248. package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
  249. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +83 -6
  250. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
  251. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
  252. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
  253. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +27 -0
  254. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
  255. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
  256. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
  257. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +642 -197
  258. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +61 -104
  259. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +628 -0
  260. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
  261. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +978 -184
  262. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +519 -128
  263. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +77 -3
  264. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
  265. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
  266. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +131 -20
  267. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
  268. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
  269. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +36 -53
  270. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
  271. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
  272. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +243 -7
  273. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +121 -1
  274. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +60 -0
  275. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
  277. package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
  278. package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
  279. package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
  280. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
  281. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +229 -32
  282. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
  283. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
  284. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
  285. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
  286. package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
  287. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
  288. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
  289. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
  290. package/scripts/setup-claude-smart.sh +8 -3
  291. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
  292. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
  293. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
  294. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
  295. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
  296. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
  297. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
  298. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
  299. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
  300. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
  301. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
  302. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
  303. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
  304. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
  305. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
  306. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
  307. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
  308. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
  309. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
  310. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
  311. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
  312. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
  313. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
  314. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
  315. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
  316. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
  317. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
  318. /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
  319. /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
  320. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
  321. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
  322. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
  323. /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
@@ -20,6 +20,13 @@ def validate_publish_user_interaction_request(
20
20
  if not request.interaction_data_list:
21
21
  return False, "No interaction data provided"
22
22
 
23
+ # Defense-in-depth: session_id is a required NonEmptyStr on
24
+ # PublishUserInteractionRequest, so the normal validated API path already
25
+ # rejects empty/missing values with a 422. This guard additionally covers
26
+ # paths that bypass Pydantic validation (e.g. ``model_construct``).
27
+ if not request.session_id or not request.session_id.strip():
28
+ return False, "session_id is required and cannot be empty"
29
+
23
30
  for interaction_data in request.interaction_data_list:
24
31
  if (
25
32
  interaction_data.user_action != UserActionType.NONE
@@ -17,7 +17,9 @@ class RequestContext:
17
17
  self.storage_base_dir = storage_base_dir
18
18
  cls = get_configurator_class()
19
19
  self.configurator = configurator or cls(org_id, base_dir=storage_base_dir)
20
- self.prompt_manager = PromptManager()
20
+ self.prompt_manager = PromptManager(
21
+ extra_prompt_bank_paths=self.configurator.get_prompt_bank_paths()
22
+ )
21
23
  self.storage = self.configurator.create_storage(
22
24
  storage_config=self.configurator.get_current_storage_configuration(),
23
25
  )
@@ -0,0 +1,173 @@
1
+ """OSS emission helpers: translate billing signals into usage_events.
2
+
3
+ The single source of truth for each billing event's name/category/fields. Plain
4
+ primitive signatures (no reflexio_ext types) so OSS call sites — the generation
5
+ service and search endpoints — can use them directly. Each is a thin, non-blocking
6
+ wrapper over the ``record_usage_event`` hook (which only enqueues). No DB I/O.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from reflexio.server.usage_metrics import record_usage_event
12
+
13
+ _INTERNAL = (
14
+ "internal" # == BillingCallerType.INTERNAL.value (kept literal; OSS stays clean)
15
+ )
16
+
17
+
18
+ def record_extraction_tokens(
19
+ *,
20
+ org_id: str,
21
+ billing_input_tokens: int,
22
+ prompt_tokens: int,
23
+ completion_tokens: int,
24
+ platform_llm: bool | None,
25
+ platform_storage: bool | None,
26
+ pipeline: str | None = None,
27
+ request_id: str | None = None,
28
+ session_id: str | None = None,
29
+ ) -> None:
30
+ """Emit the Learning cost facet — call only when extraction fired.
31
+
32
+ No-op when ``billing_input_tokens <= 0``.
33
+
34
+ Args:
35
+ org_id: Organisation identifier.
36
+ billing_input_tokens: Input-anchored token count (the metered basis).
37
+ prompt_tokens: Real provider prompt tokens (COGS; not billed to customer).
38
+ completion_tokens: Real provider completion tokens (COGS; not billed).
39
+ platform_llm: True iff the platform supplies the LLM for this org.
40
+ platform_storage: True iff the platform supplies storage; None defers to rollup.
41
+ pipeline: Optional pipeline tag (e.g. ``"profile"``).
42
+ request_id: Optional request correlation ID.
43
+ session_id: Optional session ID.
44
+ """
45
+ if billing_input_tokens <= 0:
46
+ return
47
+ record_usage_event(
48
+ org_id=org_id,
49
+ event_name="extraction_tokens",
50
+ event_category="learning",
51
+ pipeline=pipeline,
52
+ request_id=request_id,
53
+ session_id=session_id,
54
+ count_value=billing_input_tokens,
55
+ prompt_tokens=prompt_tokens,
56
+ completion_tokens=completion_tokens,
57
+ billing_input_tokens=billing_input_tokens,
58
+ platform_llm=platform_llm,
59
+ platform_storage=platform_storage,
60
+ caller_type=_INTERNAL,
61
+ )
62
+
63
+
64
+ def record_learnings_generated(
65
+ *,
66
+ org_id: str,
67
+ count: int,
68
+ platform_llm: bool | None,
69
+ platform_storage: bool | None,
70
+ pipeline: str | None = None,
71
+ request_id: str | None = None,
72
+ session_id: str | None = None,
73
+ ) -> None:
74
+ """Emit the Learning value facet — number of profiles/playbooks generated.
75
+
76
+ No-op when ``count <= 0``.
77
+
78
+ Args:
79
+ org_id: Organisation identifier.
80
+ count: Number of learnings generated in this run.
81
+ platform_llm: True iff the platform supplies the LLM for this org.
82
+ platform_storage: True iff the platform supplies storage; None defers to rollup.
83
+ pipeline: Optional pipeline tag (e.g. ``"playbook"``).
84
+ request_id: Optional request correlation ID.
85
+ session_id: Optional session ID.
86
+ """
87
+ if count <= 0:
88
+ return
89
+ record_usage_event(
90
+ org_id=org_id,
91
+ event_name="learnings_generated",
92
+ event_category="learning",
93
+ pipeline=pipeline,
94
+ request_id=request_id,
95
+ session_id=session_id,
96
+ count_value=count,
97
+ platform_llm=platform_llm,
98
+ platform_storage=platform_storage,
99
+ caller_type=_INTERNAL,
100
+ )
101
+
102
+
103
+ def record_applied_learnings(
104
+ *,
105
+ org_id: str,
106
+ surfaced_count: int,
107
+ caller_type: str,
108
+ platform_llm: bool | None,
109
+ platform_storage: bool | None,
110
+ pipeline: str | None = None,
111
+ request_id: str | None = None,
112
+ session_id: str | None = None,
113
+ ) -> None:
114
+ """Emit the Application line — surfaced top-K learnings.
115
+
116
+ No-op unless ``caller_type == "production_agent"`` AND ``surfaced_count > 0``.
117
+
118
+ Args:
119
+ org_id: Organisation identifier.
120
+ surfaced_count: Number of learnings surfaced in the search response.
121
+ caller_type: Caller classification string (e.g. ``"production_agent"``).
122
+ platform_llm: True iff the platform supplies the LLM for this org.
123
+ platform_storage: True iff the platform supplies storage; None defers to rollup.
124
+ pipeline: Optional pipeline tag.
125
+ request_id: Optional request correlation ID.
126
+ session_id: Optional session ID.
127
+ """
128
+ if caller_type != "production_agent" or surfaced_count <= 0:
129
+ return
130
+ record_usage_event(
131
+ org_id=org_id,
132
+ event_name="learning_applied",
133
+ event_category="application",
134
+ pipeline=pipeline,
135
+ request_id=request_id,
136
+ session_id=session_id,
137
+ count_value=surfaced_count,
138
+ platform_llm=platform_llm,
139
+ platform_storage=platform_storage,
140
+ caller_type=caller_type,
141
+ )
142
+
143
+
144
+ def record_search_request(
145
+ *,
146
+ org_id: str,
147
+ caller_type: str,
148
+ request_id: str | None = None,
149
+ session_id: str | None = None,
150
+ ) -> None:
151
+ """Emit one analytics-only search request count for production agents.
152
+
153
+ No-op unless ``caller_type == "production_agent"``. Unlike
154
+ :func:`record_applied_learnings`, empty search responses still count because
155
+ this measures requests made, not learnings surfaced.
156
+
157
+ Args:
158
+ org_id: Organisation identifier.
159
+ caller_type: Caller classification string.
160
+ request_id: Optional request correlation ID.
161
+ session_id: Optional session ID.
162
+ """
163
+ if caller_type != "production_agent":
164
+ return
165
+ record_usage_event(
166
+ org_id=org_id,
167
+ event_name="search_request",
168
+ event_category="application",
169
+ request_id=request_id,
170
+ session_id=session_id,
171
+ count_value=1,
172
+ caller_type=caller_type,
173
+ )
@@ -0,0 +1,64 @@
1
+ """OSS-pure billing signal helpers (no reflexio_ext imports).
2
+
3
+ Single source of truth for whether the platform supplies the LLM, so the OSS
4
+ generation service and the enterprise attribution resolver never diverge.
5
+ Also provides the one canonical input-anchored tokenizer for the whole platform.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Mapping
11
+ from typing import Any
12
+
13
+ import tiktoken
14
+
15
+ # One fixed canonical encoding for the whole platform. Changing this is a
16
+ # price-book-affecting decision; do not make it model-dependent.
17
+ _CANONICAL_ENCODING_NAME = "cl100k_base"
18
+ _encoding = tiktoken.get_encoding(_CANONICAL_ENCODING_NAME)
19
+
20
+
21
+ def count_input_tokens(text: str) -> int:
22
+ """Count billable input tokens for ``text`` under the canonical cl100k_base encoding.
23
+
24
+ This is the single canonical tokenizer for the whole platform. Enterprise
25
+ code delegates here so OSS and enterprise always use the same encoding.
26
+
27
+ Args:
28
+ text (str): The input text to tokenize.
29
+
30
+ Returns:
31
+ int: Number of tokens under cl100k_base, or 0 for empty input.
32
+ """
33
+ if not text:
34
+ return 0
35
+ return len(_encoding.encode(text, disallowed_special=()))
36
+
37
+
38
+ def platform_llm_from_config(config: Any) -> bool:
39
+ """Return True iff Reflexio (not the customer) supplies the LLM for ``config``.
40
+
41
+ A populated ``api_key_config`` provider sub-config means BYO-LLM → False.
42
+ A missing/empty ``api_key_config`` means platform-supplied → True.
43
+
44
+ Args:
45
+ config: The org's resolved ``Config`` object, or None.
46
+
47
+ Returns:
48
+ bool: True when the platform supplies the LLM; False when the customer
49
+ has configured a BYO provider key.
50
+ """
51
+ api_key_config = getattr(config, "api_key_config", None)
52
+ if api_key_config is None:
53
+ return True
54
+ data = (
55
+ api_key_config.model_dump(exclude_none=True)
56
+ if hasattr(api_key_config, "model_dump")
57
+ else {}
58
+ )
59
+ # Guard against a model_dump that returns a non-mapping: without this,
60
+ # `.values()` would raise. Default to platform-supplied (True) on a shape
61
+ # we don't understand rather than misclassifying the org as BYO-LLM.
62
+ if not isinstance(data, Mapping):
63
+ return True
64
+ return not any(bool(v) for v in data.values())
@@ -8,6 +8,7 @@ from typing import Any, Final
8
8
  from cachetools import TTLCache
9
9
 
10
10
  from reflexio.lib.reflexio_lib import Reflexio
11
+ from reflexio.server.tracing import profile_step
11
12
 
12
13
  logger = logging.getLogger(__name__)
13
14
 
@@ -112,8 +113,14 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
112
113
 
113
114
  # Cache lookup — held briefly to extract the entry, then released
114
115
  # before doing any I/O (file stat, DB query) for the version probe.
115
- with _reflexio_cache_lock:
116
- entry: _CacheEntry | None = _reflexio_cache.get(cache_key)
116
+ with profile_step("reflexio.cache.lookup") as span:
117
+ with _reflexio_cache_lock:
118
+ entry: _CacheEntry | None = _reflexio_cache.get(cache_key)
119
+ span.set_data("cache_hit", entry is not None)
120
+ span.set_data(
121
+ "has_cached_version",
122
+ entry is not None and entry.cached_version is not None,
123
+ )
117
124
 
118
125
  if entry is not None:
119
126
  cached_version = entry.cached_version
@@ -122,7 +129,13 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
122
129
  # rely on TTL + explicit invalidation instead.
123
130
  if cached_version is None:
124
131
  return entry.reflexio
125
- current_version = _probe_version_safe(entry.reflexio)
132
+ with profile_step("reflexio.cache.version_probe", cache_state="hit") as span:
133
+ current_version = _probe_version_safe(entry.reflexio)
134
+ span.set_data("probe_failed", current_version is _PROBE_FAILED)
135
+ span.set_data(
136
+ "probe_supported",
137
+ current_version is not None and current_version is not _PROBE_FAILED,
138
+ )
126
139
  # Transient probe failure: keep the cached instance for this
127
140
  # request but DON'T mutate the stamp — the next request will
128
141
  # try again, so a brief outage doesn't permanently disable
@@ -134,15 +147,27 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
134
147
  # Stale entry. Evict only if the cached version still matches
135
148
  # the one we just compared against — another thread may have
136
149
  # already replaced the entry while we were probing.
137
- with _reflexio_cache_lock:
138
- existing = _reflexio_cache.get(cache_key)
139
- if existing is not None and existing.cached_version == cached_version:
140
- del _reflexio_cache[cache_key]
150
+ with profile_step("reflexio.cache.evict_stale") as span:
151
+ with _reflexio_cache_lock:
152
+ existing = _reflexio_cache.get(cache_key)
153
+ evicted = (
154
+ existing is not None and existing.cached_version == cached_version
155
+ )
156
+ if evicted:
157
+ del _reflexio_cache[cache_key]
158
+ span.set_data("evicted", evicted)
141
159
 
142
160
  # Cache miss (or just-evicted stale entry) - create a new instance
143
161
  # outside the lock to avoid blocking concurrent requests for other orgs.
144
- reflexio = Reflexio(org_id=org_id, storage_base_dir=storage_base_dir)
145
- new_version = _probe_version_safe(reflexio)
162
+ with profile_step("reflexio.cache.construct"):
163
+ reflexio = Reflexio(org_id=org_id, storage_base_dir=storage_base_dir)
164
+ with profile_step("reflexio.cache.version_probe", cache_state="miss") as span:
165
+ new_version = _probe_version_safe(reflexio)
166
+ span.set_data("probe_failed", new_version is _PROBE_FAILED)
167
+ span.set_data(
168
+ "probe_supported",
169
+ new_version is not None and new_version is not _PROBE_FAILED,
170
+ )
146
171
 
147
172
  # Construction-time probe failure: serve this request from the
148
173
  # newly-built instance but DON'T cache it. Caching with
@@ -159,13 +184,18 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
159
184
  cached_version=new_version, # type: ignore[arg-type]
160
185
  )
161
186
 
162
- with _reflexio_cache_lock:
163
- # Double-check in case another thread populated while we were constructing.
164
- existing = _reflexio_cache.get(cache_key)
165
- if existing is None:
166
- _reflexio_cache[cache_key] = new_entry
167
- return reflexio
168
- return existing.reflexio
187
+ with profile_step("reflexio.cache.store") as span:
188
+ with _reflexio_cache_lock:
189
+ # Double-check in case another thread populated while we were constructing.
190
+ existing = _reflexio_cache.get(cache_key)
191
+ stored = existing is None
192
+ if stored:
193
+ _reflexio_cache[cache_key] = new_entry
194
+ result = reflexio
195
+ else:
196
+ result = existing.reflexio
197
+ span.set_data("stored", stored)
198
+ return result
169
199
 
170
200
 
171
201
  def invalidate_reflexio_cache(org_id: str, storage_base_dir: str | None = None) -> bool:
@@ -0,0 +1,65 @@
1
+ """Small helpers for parsing environment-backed settings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import Mapping
7
+
8
+
9
+ def env_get(env: Mapping[str, str], name: str) -> str:
10
+ """Return a stripped environment value or an empty string."""
11
+ return str(env.get(name, "")).strip()
12
+
13
+
14
+ def env_str(
15
+ name: str,
16
+ default: str = "",
17
+ *,
18
+ env: Mapping[str, str] | None = None,
19
+ ) -> str:
20
+ """Return an environment value, treating unset OR blank as the default.
21
+
22
+ Unlike ``os.getenv(name, default)``, an empty / whitespace-only value
23
+ resolves to ``default`` rather than passing the empty string through. This
24
+ closes the "set to blank behaves differently from unset" gap: a ``KEY=``
25
+ line (which python-dotenv exports as ``""``) and an absent key both yield
26
+ the default, so callers never silently receive ``""`` where they expected a
27
+ real fallback (e.g. an ``int()``/``float()`` parse that would crash on ``""``).
28
+
29
+ Args:
30
+ name: Environment variable name.
31
+ default: Value returned when the variable is unset or blank.
32
+ env: Mapping to read from; defaults to ``os.environ``.
33
+
34
+ Returns:
35
+ The stripped environment value, or ``default`` when unset/blank.
36
+ """
37
+ raw = (env if env is not None else os.environ).get(name)
38
+ return raw.strip() if raw and raw.strip() else default
39
+
40
+
41
+ def env_required(env: Mapping[str, str], name: str) -> str:
42
+ """Return a required environment value or raise a startup error."""
43
+ value = env_get(env, name)
44
+ if not value:
45
+ raise RuntimeError(f"{name} is required for enterprise startup")
46
+ return value
47
+
48
+
49
+ def env_required_literal(
50
+ env: Mapping[str, str],
51
+ name: str,
52
+ *,
53
+ allowed: tuple[str, ...],
54
+ ) -> str:
55
+ """Return a required lowercased value constrained to allowed literals."""
56
+ value = env_required(env, name).lower()
57
+ if value not in allowed:
58
+ allowed_text = " or ".join(repr(item) for item in allowed)
59
+ raise RuntimeError(f"{name} must be {allowed_text} (got {value!r})")
60
+ return value
61
+
62
+
63
+ def env_truthy(value: str) -> bool:
64
+ """Return whether a string environment value is truthy."""
65
+ return value.strip().lower() in {"1", "true", "yes", "on"}
@@ -34,6 +34,16 @@ _SUPPORTED_MODELS = {
34
34
  _ACTIVE_MODEL: str | None = None
35
35
  _ACTIVE_MODEL_LOCK = threading.Lock()
36
36
 
37
+ # The underlying sentence-transformers / ONNX embedders share internal buffers
38
+ # and are NOT thread-safe: concurrent ``.embed()`` calls interleave and corrupt
39
+ # each other's padding/attention tensors (observed under concurrent publishes as
40
+ # ``RuntimeError: The size of tensor a (62) must match the size of tensor b (61)
41
+ # at non-singleton dimension 1``). The ``_ENCODE_SEMAPHORE`` below only caps how
42
+ # many requests run at once (for memory), it does NOT serialize them, so this
43
+ # lock guards the actual model inference. Throughput comes from micro-batch
44
+ # coalescing (many texts per encode), not from parallel encodes on one model.
45
+ _MODEL_ENCODE_LOCK = threading.Lock()
46
+
37
47
  DEFAULT_OSS_EMBEDDING_MODEL = MINILM_MODEL
38
48
 
39
49
  # Bound how many embed/encode calls run at once. The endpoint is a sync ``def``
@@ -314,10 +324,15 @@ def _encode_texts_now(model: str, texts: list[str]) -> list[list[float]]:
314
324
  )
315
325
  semaphore.acquire()
316
326
  try:
317
- if is_nomic_model(model):
318
- return NomicEmbedder.get().embed(texts)
319
- if model == MINILM_MODEL:
320
- return LocalEmbedder.get().embed(texts)
327
+ # Serialize the actual inference: the shared embedder models are not
328
+ # thread-safe, so concurrent encodes race and produce tensor-shape
329
+ # errors. The semaphore (above) bounds memory; this lock bounds the
330
+ # model to one in-flight encode at a time.
331
+ with _MODEL_ENCODE_LOCK:
332
+ if is_nomic_model(model):
333
+ return NomicEmbedder.get().embed(texts)
334
+ if model == MINILM_MODEL:
335
+ return LocalEmbedder.get().embed(texts)
321
336
  raise HTTPException(status_code=400, detail=f"Unsupported model: {model}")
322
337
  finally:
323
338
  semaphore.release()