claude-smart 0.2.45 → 0.2.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +47 -10
  3. package/bin/claude-smart.js +535 -23
  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 +27 -6
  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/layout.tsx +20 -0
  16. package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
  17. package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
  18. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
  19. package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
  20. package/plugin/dashboard/lib/reflexio-client.ts +16 -0
  21. package/plugin/dashboard/lib/status.ts +10 -3
  22. package/plugin/dashboard/lib/types.ts +18 -6
  23. package/plugin/hooks/codex-hooks.json +0 -1
  24. package/plugin/opencode/assistant-buffer.ts +108 -0
  25. package/plugin/opencode/dist/assistant-buffer.js +96 -0
  26. package/plugin/opencode/dist/internal.js +12 -0
  27. package/plugin/opencode/dist/payload.js +85 -0
  28. package/plugin/opencode/dist/server.mjs +232 -0
  29. package/plugin/opencode/internal.ts +18 -0
  30. package/plugin/opencode/package.json +3 -0
  31. package/plugin/opencode/payload.ts +90 -0
  32. package/plugin/opencode/server.mts +251 -0
  33. package/plugin/opencode/tsconfig.json +13 -0
  34. package/plugin/pyproject.toml +25 -4
  35. package/plugin/scripts/_lib.sh +5 -1
  36. package/plugin/scripts/backend-service.sh +22 -2
  37. package/plugin/scripts/ensure-plugin-root.sh +4 -4
  38. package/plugin/scripts/hook_entry.sh +1 -1
  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 +17 -9
  43. package/plugin/src/README.md +1 -1
  44. package/plugin/src/claude_smart/cli.py +498 -12
  45. package/plugin/src/claude_smart/context_format.py +9 -9
  46. package/plugin/src/claude_smart/cs_cite.py +66 -19
  47. package/plugin/src/claude_smart/env_config.py +12 -4
  48. package/plugin/src/claude_smart/events/session_start.py +26 -7
  49. package/plugin/src/claude_smart/events/stop.py +1 -1
  50. package/plugin/src/claude_smart/ids.py +1 -1
  51. package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
  52. package/plugin/src/claude_smart/runtime.py +7 -1
  53. package/plugin/uv.lock +5 -5
  54. package/plugin/vendor/reflexio/.env.example +54 -30
  55. package/plugin/vendor/reflexio/README.md +17 -11
  56. package/plugin/vendor/reflexio/pyproject.toml +13 -1
  57. package/plugin/vendor/reflexio/reflexio/README.md +4 -1
  58. package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
  59. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
  60. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
  63. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
  64. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +212 -52
  65. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
  66. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
  67. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
  68. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
  69. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
  70. package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
  71. package/plugin/vendor/reflexio/reflexio/client/client.py +154 -13
  72. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
  142. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
  143. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
  144. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
  145. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +246 -10
  146. package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
  147. package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
  148. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +17 -5
  149. package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
  150. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +224 -21
  151. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
  152. package/plugin/vendor/reflexio/reflexio/lib/_search.py +52 -25
  153. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +11 -1
  154. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
  155. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
  156. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
  157. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
  158. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
  159. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
  160. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
  161. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +97 -3
  162. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
  163. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
  164. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +104 -7
  165. package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
  166. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
  167. package/plugin/vendor/reflexio/reflexio/server/README.md +58 -33
  168. package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
  169. package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
  170. package/plugin/vendor/reflexio/reflexio/server/api.py +899 -103
  171. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
  172. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +4 -2
  173. package/plugin/vendor/reflexio/reflexio/server/auth.py +90 -0
  174. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
  175. package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
  176. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
  177. package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
  178. package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
  179. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
  180. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
  181. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
  182. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
  183. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
  184. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
  185. package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
  186. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
  187. package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
  188. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
  189. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
  190. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
  191. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
  192. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
  193. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
  194. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
  195. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
  196. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
  197. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
  198. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +278 -0
  199. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
  200. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
  201. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
  202. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
  203. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
  204. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
  205. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
  206. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
  207. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
  208. package/plugin/vendor/reflexio/reflexio/server/services/README.md +28 -11
  209. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
  210. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
  211. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
  212. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
  213. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
  214. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
  215. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
  216. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
  217. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
  218. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
  219. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
  220. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
  221. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
  222. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
  223. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
  224. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
  225. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
  226. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
  227. package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
  228. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
  229. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
  230. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
  231. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +47 -16
  232. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
  233. package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
  234. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
  235. package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
  236. package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
  237. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +219 -0
  238. package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
  239. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
  240. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +18 -11
  241. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
  242. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
  243. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +334 -178
  244. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
  245. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
  246. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
  247. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
  248. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
  249. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
  250. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
  251. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
  252. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
  253. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
  254. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
  255. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +31 -22
  256. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
  257. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
  258. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
  259. package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
  260. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
  261. package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
  262. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
  263. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +99 -6
  264. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
  265. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
  266. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
  267. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +31 -0
  268. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
  269. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
  270. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +16 -2
  271. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
  272. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +644 -197
  273. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +106 -119
  274. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
  275. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +630 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
  277. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +31 -1369
  278. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +756 -210
  279. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +148 -34
  280. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
  281. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
  282. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
  283. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
  284. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
  285. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
  286. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
  287. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
  288. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +150 -23
  289. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
  290. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
  291. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +40 -57
  292. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
  293. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
  294. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
  295. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +4 -677
  296. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +134 -1
  297. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +62 -0
  298. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
  299. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
  300. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
  301. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
  302. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
  303. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
  304. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
  305. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
  306. package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
  307. package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
  308. package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
  309. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
  310. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +372 -34
  311. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
  312. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
  313. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
  314. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
  315. package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
  316. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
  317. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
  318. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
  319. package/scripts/setup-claude-smart.sh +8 -3
  320. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
  321. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
  322. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
  323. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
  324. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
  325. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
  326. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
  327. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
  328. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
  329. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
  330. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
  331. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
  332. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
  333. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
  334. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
  335. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
  336. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
  337. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
  338. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
  339. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
  340. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
  341. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
  342. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
  343. package/plugin/vendor/reflexio/reflexio/server/_auth.py +0 -25
  344. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
  345. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
  346. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
  347. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
  348. /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
  349. /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
  350. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
  351. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
  352. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
  353. /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
@@ -10,10 +10,14 @@ from reflexio.server.services.agent_success_evaluation import _eval_health
10
10
  from reflexio.server.services.agent_success_evaluation.agent_success_evaluation_utils import (
11
11
  AgentSuccessEvaluationRequest,
12
12
  )
13
- from reflexio.server.services.agent_success_evaluation.agent_success_evaluator import (
13
+ from reflexio.server.services.agent_success_evaluation.components.evaluator import (
14
14
  AgentSuccessEvaluator,
15
15
  )
16
16
  from reflexio.server.services.base_generation_service import BaseGenerationService
17
+ from reflexio.server.services.extractor_interaction_utils import (
18
+ filter_interactions_by_source,
19
+ get_effective_source_filter,
20
+ )
17
21
 
18
22
  logger = logging.getLogger(__name__)
19
23
 
@@ -23,12 +27,14 @@ class AgentSuccessGenerationServiceConfig:
23
27
  """Runtime configuration for agent success evaluation service shared across all extractors.
24
28
 
25
29
  Attributes:
30
+ user_id: The user whose session slice is being evaluated
26
31
  session_id: The session being evaluated
27
32
  agent_version: The agent version
28
33
  request_interaction_data_models: The interactions to evaluate
29
34
  source: Source of the interactions
30
35
  """
31
36
 
37
+ user_id: str
32
38
  session_id: str
33
39
  agent_version: str
34
40
  request_interaction_data_models: list[RequestInteractionDataModel]
@@ -76,6 +82,7 @@ class AgentSuccessEvaluationService(
76
82
  AgentSuccessGenerationServiceConfig object
77
83
  """
78
84
  return AgentSuccessGenerationServiceConfig(
85
+ user_id=request.user_id,
79
86
  session_id=request.session_id,
80
87
  agent_version=request.agent_version,
81
88
  request_interaction_data_models=request.request_interaction_data_models,
@@ -115,6 +122,34 @@ class AgentSuccessEvaluationService(
115
122
  agent_context=self.configurator.get_agent_context(),
116
123
  )
117
124
 
125
+ def _collect_scoped_interactions_for_precheck(
126
+ self, extractor_config: AgentSuccessConfig
127
+ ) -> tuple[list[RequestInteractionDataModel], AgentSuccessConfig]:
128
+ """Use the session slice already loaded by the group runner.
129
+
130
+ The generic pre-check queries storage for learnable interaction windows,
131
+ which intentionally excludes evaluation-only requests. Agent-success
132
+ evaluation is allowed to run on evaluation-only sessions, so its
133
+ pre-check must inspect the request models passed to this service.
134
+ """
135
+ service_config = self.service_config
136
+ if service_config is None:
137
+ raise RuntimeError("Agent success pre-check requires service_config")
138
+ session_data_models = service_config.request_interaction_data_models
139
+ should_skip, source_filter = get_effective_source_filter(
140
+ extractor_config, service_config.source
141
+ )
142
+ if should_skip:
143
+ self._last_precheck_sessions = []
144
+ return [], extractor_config
145
+
146
+ scoped_models = filter_interactions_by_source(
147
+ session_data_models,
148
+ source_filter,
149
+ )
150
+ self._last_precheck_sessions = scoped_models
151
+ return scoped_models, extractor_config
152
+
118
153
  def _process_results(self, results: list) -> None:
119
154
  """
120
155
  Process and save agent success evaluation results.
@@ -20,6 +20,7 @@ from reflexio.models.api_schema.internal_schema import RequestInteractionDataMod
20
20
  from reflexio.models.api_schema.service_schemas import Status
21
21
  from reflexio.server.api_endpoints.request_context import RequestContext
22
22
  from reflexio.server.llm.litellm_client import LiteLLMClient
23
+ from reflexio.server.llm.token_accounting import RunTokenTotals
23
24
  from reflexio.server.services.extraction.outcome import ExtractionOutcome
24
25
  from reflexio.server.services.extractor_config_utils import (
25
26
  filter_extractor_configs,
@@ -165,6 +166,10 @@ class BaseGenerationService(
165
166
  ABC,
166
167
  Generic[TExtractorConfig, TExtractor, TGenerationServiceConfig, TRequest], # noqa: UP046
167
168
  ):
169
+ # Only profile/playbook GENERATION services bill the ② Learning line;
170
+ # reflection/consolidation/aggregation/evaluation are bundled, not metered.
171
+ # Default is False so any future subclass is safe by default (opt-IN).
172
+ EMITS_LEARNING_BILLING: bool = False
168
173
  """
169
174
  Base class for generation services that run one configured extractor.
170
175
 
@@ -209,6 +214,11 @@ class BaseGenerationService(
209
214
  "timed_out": 0,
210
215
  }
211
216
  self._last_extraction_run_ids: list[str] = []
217
+ self._last_token_totals: RunTokenTotals | None = None
218
+ # Window fetched by the should-run gate (_collect_scoped_interactions_for_precheck),
219
+ # stashed so the billing path (_extraction_input_text) can reuse it instead of
220
+ # re-querying storage. None when the gate did not run (bypass paths).
221
+ self._last_precheck_sessions: list[Any] | None = None
212
222
 
213
223
  def _usage_pipeline(self) -> str | None:
214
224
  service_name = self._get_service_name()
@@ -252,6 +262,139 @@ class BaseGenerationService(
252
262
  metadata=metadata,
253
263
  )
254
264
 
265
+ def _extraction_input_text(self, prepared: "PreparedGenerationRun[Any]") -> str:
266
+ """Return the interaction content fed to the extractor as a single string.
267
+
268
+ Replays the same storage query the extractor uses (same user_id, source,
269
+ window params) so ``count_input_tokens`` sees the same text the LLM saw.
270
+ Returns an empty string when storage is unavailable or no interactions exist.
271
+
272
+ Args:
273
+ prepared: The prepared generation run, used to access extractor_config
274
+ for window/source parameters.
275
+
276
+ Returns:
277
+ str: Formatted interaction history string, or "" if nothing is found.
278
+ """
279
+ from reflexio.server.services.service_utils import (
280
+ format_sessions_to_history_string,
281
+ )
282
+
283
+ if self.storage is None or self.service_config is None:
284
+ return ""
285
+ # Reuse the window the should-run gate already fetched
286
+ # (_collect_scoped_interactions_for_precheck stashed it). Same query params,
287
+ # so the token count is byte-identical — and we avoid a second storage read.
288
+ if self._last_precheck_sessions is not None:
289
+ return format_sessions_to_history_string(self._last_precheck_sessions)
290
+ # Fallback storage read: used ONLY when the gate did not pre-fetch
291
+ # (bypass paths: auto_run=False, force_extraction, skip_should_run_check,
292
+ # mock mode). Re-fetches the extractor's input window so billing token
293
+ # counting sees exactly the text the LLM saw.
294
+ try:
295
+ root_config = self.request_context.configurator.get_config()
296
+ global_window_size = (
297
+ getattr(root_config, "window_size", None) if root_config else None
298
+ )
299
+ global_stride_size = (
300
+ getattr(root_config, "stride_size", None) if root_config else None
301
+ )
302
+ window_size, _ = get_extractor_window_params(
303
+ prepared.extractor_config, global_window_size, global_stride_size
304
+ )
305
+ should_skip, effective_source = get_effective_source_filter(
306
+ prepared.extractor_config, getattr(self.service_config, "source", None)
307
+ )
308
+ if should_skip:
309
+ return ""
310
+ session_data_models, _ = self.storage.get_last_k_interactions_grouped(
311
+ user_id=getattr(self.service_config, "user_id", None),
312
+ k=window_size,
313
+ sources=effective_source,
314
+ start_time=getattr(self.service_config, "rerun_start_time", None),
315
+ end_time=getattr(self.service_config, "rerun_end_time", None),
316
+ **self._get_precheck_interaction_query_kwargs(),
317
+ )
318
+ return format_sessions_to_history_string(session_data_models)
319
+ except Exception:
320
+ logger.warning(
321
+ "_extraction_input_text failed; billing_input_tokens will be 0",
322
+ exc_info=True,
323
+ )
324
+ return ""
325
+
326
+ def _record_billing_learning_events(
327
+ self, *, prepared: "PreparedGenerationRun[Any]", generated_count: int
328
+ ) -> None:
329
+ """Emit the ② Learning billing events. Called ONLY when extraction fired.
330
+
331
+ Emits ``learnings_generated`` (value facet) and ``extraction_tokens``
332
+ (cost facet) via the OSS emission helpers. ``platform_storage`` is left
333
+ ``None`` here and resolved enterprise-side at rollup (Phase 1).
334
+
335
+ Gated by ``EMITS_LEARNING_BILLING`` — only profile/playbook generation
336
+ services opt in. Evaluation, reflection, consolidation and aggregation
337
+ are bundled and must NOT separately meter the ② Learning line.
338
+
339
+ Args:
340
+ prepared: The prepared generation run (used for input-text computation).
341
+ generated_count: Number of learnings produced by this extraction run.
342
+ """
343
+ if not self.EMITS_LEARNING_BILLING:
344
+ return
345
+
346
+ try:
347
+ from reflexio.server.billing_meter import (
348
+ record_extraction_tokens,
349
+ record_learnings_generated,
350
+ )
351
+ from reflexio.server.billing_signals import (
352
+ count_input_tokens,
353
+ platform_llm_from_config,
354
+ )
355
+
356
+ config = self.request_context.configurator.get_config()
357
+ platform_llm = platform_llm_from_config(config)
358
+ ctx = self._usage_context()
359
+
360
+ # session_id is intentionally not passed: the generation path has no
361
+ # session_id source. _usage_context() never includes it, and neither
362
+ # the Profile/Playbook service configs nor their requests carry a
363
+ # session_id (unlike the Application-line path in server/api.py, which
364
+ # reads request_id/session_id off the search request payload). Learning
365
+ # events therefore meter without session attribution by design.
366
+
367
+ # ② Learning — value: learnings generated (helper no-ops on count <= 0).
368
+ record_learnings_generated(
369
+ org_id=ctx["org_id"],
370
+ count=generated_count,
371
+ platform_llm=platform_llm,
372
+ platform_storage=None,
373
+ pipeline=ctx.get("pipeline"),
374
+ request_id=ctx.get("request_id"),
375
+ )
376
+
377
+ # ② Learning — cost: input-anchored extraction tokens + real provider tokens.
378
+ totals = self._last_token_totals or RunTokenTotals()
379
+ billing_input_tokens = count_input_tokens(
380
+ self._extraction_input_text(prepared)
381
+ )
382
+ record_extraction_tokens(
383
+ org_id=ctx["org_id"],
384
+ billing_input_tokens=billing_input_tokens,
385
+ prompt_tokens=totals.prompt_tokens,
386
+ completion_tokens=totals.completion_tokens,
387
+ platform_llm=platform_llm,
388
+ platform_storage=None,
389
+ pipeline=ctx.get("pipeline"),
390
+ request_id=ctx.get("request_id"),
391
+ )
392
+ except Exception:
393
+ logger.warning(
394
+ "_record_billing_learning_events failed; billing learning events not emitted",
395
+ exc_info=True,
396
+ )
397
+
255
398
  @staticmethod
256
399
  def _count_generated_results(result: Any) -> int:
257
400
  if isinstance(result, list):
@@ -675,6 +818,7 @@ class BaseGenerationService(
675
818
  },
676
819
  )
677
820
  self._last_extraction_run_ids = []
821
+ self._last_token_totals = None
678
822
  result = self._execute_extractor(
679
823
  prepared.extractor_config, prepared.identifier
680
824
  )
@@ -704,6 +848,9 @@ class BaseGenerationService(
704
848
  ),
705
849
  },
706
850
  )
851
+ self._record_billing_learning_events(
852
+ prepared=prepared, generated_count=generated_count
853
+ )
707
854
 
708
855
  except Exception as e:
709
856
  self._record_generation_event(
@@ -737,6 +884,13 @@ class BaseGenerationService(
737
884
  Returns:
738
885
  PreparedGenerationRun when generation should proceed, otherwise None.
739
886
  """
887
+ # Reset BEFORE the should-run gate runs. The gate
888
+ # (_collect_scoped_interactions_for_precheck) stashes its fetched window
889
+ # here for the billing path to reuse. On bypass paths (auto_run=False,
890
+ # force_extraction, skip_should_run_check, mock mode) the gate never runs,
891
+ # so this stays None and billing falls back to its own fetch.
892
+ self._last_precheck_sessions = None
893
+
740
894
  self.service_config = self._load_generation_service_config(request)
741
895
 
742
896
  extractor_config = self._load_extractor_config()
@@ -834,6 +988,7 @@ class BaseGenerationService(
834
988
  if isinstance(result, ExtractionOutcome):
835
989
  if result.run_id:
836
990
  self._last_extraction_run_ids.append(result.run_id)
991
+ self._last_token_totals = result.token_totals
837
992
  if result.status == "completed" and result.items:
838
993
  return result.items
839
994
  logger.info(
@@ -1128,6 +1283,8 @@ class BaseGenerationService(
1128
1283
  extractor_config, getattr(self.service_config, "source", None)
1129
1284
  )
1130
1285
  if should_skip:
1286
+ # Stash for the billing path to reuse (same window the gate saw).
1287
+ self._last_precheck_sessions = []
1131
1288
  return [], extractor_config
1132
1289
 
1133
1290
  window_size, _ = get_extractor_window_params(
@@ -1142,6 +1299,9 @@ class BaseGenerationService(
1142
1299
  **extra_kwargs,
1143
1300
  )
1144
1301
 
1302
+ # Stash for the billing path (_extraction_input_text) to reuse instead of
1303
+ # re-querying storage for billing_input_tokens.
1304
+ self._last_precheck_sessions = session_data_models
1145
1305
  return session_data_models, extractor_config
1146
1306
 
1147
1307
  def _get_precheck_interaction_query_kwargs(self) -> dict:
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  from abc import ABC, abstractmethod
5
5
  from collections.abc import Callable
6
+ from pathlib import Path
6
7
  from typing import Any, ClassVar
7
8
 
8
9
  from pydantic import BaseModel
@@ -81,6 +82,10 @@ class BaseConfigurator(ABC):
81
82
  """Normalize raw API config payloads before Pydantic validation."""
82
83
  return config
83
84
 
85
+ def get_prompt_bank_paths(self) -> list[Path]:
86
+ """Return additional prompt banks this configurator contributes."""
87
+ return []
88
+
84
89
  def get_agent_context(self) -> str:
85
90
  context = self.get_config().agent_context_prompt
86
91
  if not context:
@@ -2,7 +2,7 @@
2
2
  Shared utilities for deduplication services.
3
3
 
4
4
  This module contains base classes and utility functions used by both
5
- ProfileDeduplicator and PlaybookConsolidator.
5
+ ProfileConsolidator and PlaybookConsolidator.
6
6
  """
7
7
 
8
8
  import logging
@@ -0,0 +1,9 @@
1
+ # evaluation_overview
2
+
3
+ Read-side aggregation module for `POST /api/get_evaluation_overview`.
4
+
5
+ - `service.py` is the request-path entry point. It loads evaluation, citation, Braintrust, and optional shadow verdict data, then composes `GetEvaluationOverviewResponse`.
6
+ - `components/` contains pure read-side aggregation helpers used by the service and focused tests.
7
+ - `eval_sampler.py` stays at the package root because regenerate jobs also use it to sample evaluation sessions.
8
+
9
+ This module mutates no core state. Keep response-shape changes in API schema tests and service integration tests.
@@ -11,6 +11,7 @@ from collections.abc import Mapping
11
11
  from dataclasses import dataclass
12
12
 
13
13
  CitationKey = tuple[str, str] # (kind, real_id) — matches PlaybookApplicationStat
14
+ SessionIdentity = tuple[str, str] # (user_id, session_id)
14
15
 
15
16
 
16
17
  @dataclass(frozen=True)
@@ -36,19 +37,19 @@ class RuleAttribution:
36
37
 
37
38
  def compute_net_sessions(
38
39
  *,
39
- citations_by_session: Mapping[str, list[CitationKey]],
40
- is_success_by_session: Mapping[str, bool],
40
+ citations_by_session: Mapping[SessionIdentity, list[CitationKey]],
41
+ is_success_by_session: Mapping[SessionIdentity, bool],
41
42
  rule_titles: Mapping[CitationKey, str],
42
43
  top_n: int,
43
44
  ) -> list[RuleAttribution]:
44
45
  """Aggregate net sessions per rule and return the top N by net_sessions.
45
46
 
46
47
  Args:
47
- citations_by_session (Mapping[str, list[CitationKey]]): For each
48
- session in the window, the list of `(kind, real_id)` citations
48
+ citations_by_session (Mapping[SessionIdentity, list[CitationKey]]): For
49
+ each user/session slice in the window, the list of `(kind, real_id)` citations
49
50
  harvested from its interactions.
50
- is_success_by_session (Mapping[str, bool]): The session's evaluated
51
- outcome. Sessions not in this map are skipped on both sides.
51
+ is_success_by_session (Mapping[SessionIdentity, bool]): The evaluated
52
+ outcome. User/session slices not in this map are skipped on both sides.
52
53
  rule_titles (Mapping[CitationKey, str]): Pre-loaded display titles
53
54
  for each rule (empty string when the underlying row is gone).
54
55
  top_n (int): How many rows to return at most. Ordering: net_sessions
@@ -65,8 +66,8 @@ def compute_net_sessions(
65
66
  # Ordering preserves caller-provided iteration order; the frontend sorts
66
67
  # the detail band by created_at when displaying.
67
68
  sessions_by_rule: dict[CitationKey, list[str]] = {}
68
- for session_id, citations in citations_by_session.items():
69
- outcome = is_success_by_session.get(session_id)
69
+ for session_key, citations in citations_by_session.items():
70
+ outcome = is_success_by_session.get(session_key)
70
71
  if outcome is None:
71
72
  continue
72
73
  # A rule cited multiple times in the same session counts once.
@@ -76,7 +77,7 @@ def compute_net_sessions(
76
77
  successes[key] = successes.get(key, 0) + 1
77
78
  else:
78
79
  failures[key] = failures.get(key, 0) + 1
79
- sessions_by_rule.setdefault(key, []).append(session_id)
80
+ sessions_by_rule.setdefault(key, []).append(session_key[1])
80
81
 
81
82
  all_keys = set(successes) | set(failures)
82
83
  rows = [
@@ -3,10 +3,9 @@
3
3
  DB-free and LLM-free. Operates on SampleCandidate tuples emitted by the
4
4
  regen worker's candidate-discovery step; returns the sampled subset.
5
5
 
6
- Stratification key: (day_bucket, F2 group). Day buckets are aligned to
7
- 86400-second boundaries. F2 group is derived via the existing
8
- `assign_group_from_metadata` so the per-day cap is honored separately
9
- for treatment / control / untagged.
6
+ Stratification key: (day_bucket, first_request_source). Day buckets are
7
+ aligned to 86400-second boundaries. The source value is sticky per session
8
+ and comes from the session's earliest request.
10
9
  """
11
10
 
12
11
  from __future__ import annotations
@@ -15,12 +14,6 @@ import random
15
14
  from collections import defaultdict
16
15
  from collections.abc import Iterable
17
16
  from dataclasses import dataclass
18
- from typing import Any
19
-
20
- from reflexio.server.services.evaluation_overview.group_aggregation import (
21
- GroupAssignment,
22
- assign_group_from_metadata,
23
- )
24
17
 
25
18
  # Day bucket width in seconds. Strata are aligned to (ts // _DAY) * _DAY.
26
19
  _DAY = 86_400
@@ -31,8 +24,7 @@ class SampleCandidate:
31
24
  """One candidate for the regen pipeline's sampling step.
32
25
 
33
26
  Carries everything the regen worker needs to dispatch a per-session
34
- evaluation, plus the first-request metadata used to assign the F2
35
- group for stratification.
27
+ evaluation, plus the first-request source used for stratification.
36
28
 
37
29
  Attributes:
38
30
  session_id: The session to evaluate.
@@ -41,8 +33,8 @@ class SampleCandidate:
41
33
  source: Optional source tag (passed through to run_group_evaluation).
42
34
  created_at: Unix epoch seconds of the session's most relevant
43
35
  timestamp; used for day-bucket alignment.
44
- first_request_metadata: The metadata dict on the session's first
45
- request, read once during candidate discovery.
36
+ first_request_source: Source on the session's first request, read once
37
+ during candidate discovery. Empty string is a real stratum.
46
38
  """
47
39
 
48
40
  session_id: str
@@ -50,27 +42,26 @@ class SampleCandidate:
50
42
  agent_version: str
51
43
  source: str | None
52
44
  created_at: int
53
- first_request_metadata: dict[str, Any]
45
+ first_request_source: str
54
46
 
55
47
 
56
- def stratify_by_day_and_group(
48
+ def stratify_by_day_and_source(
57
49
  candidates: Iterable[SampleCandidate],
58
- ) -> dict[tuple[int, GroupAssignment], list[SampleCandidate]]:
59
- """Bucket candidates by (day_bucket, F2 group).
50
+ ) -> dict[tuple[int, str], list[SampleCandidate]]:
51
+ """Bucket candidates by (day_bucket, first_request_source).
60
52
 
61
53
  Args:
62
54
  candidates: Candidate tuples emitted by the regen candidate-discovery
63
55
  step.
64
56
 
65
57
  Returns:
66
- Dict from (day_bucket_start, group) to the candidate list for that
58
+ Dict from (day_bucket_start, source) to the candidate list for that
67
59
  stratum. Empty strata are absent; the dict has only populated keys.
68
60
  """
69
- strata: dict[tuple[int, GroupAssignment], list[SampleCandidate]] = defaultdict(list)
61
+ strata: dict[tuple[int, str], list[SampleCandidate]] = defaultdict(list)
70
62
  for c in candidates:
71
63
  day_bucket = (c.created_at // _DAY) * _DAY
72
- group = assign_group_from_metadata(c.first_request_metadata)
73
- strata[(day_bucket, group)].append(c)
64
+ strata[(day_bucket, c.first_request_source)].append(c)
74
65
  return dict(strata)
75
66
 
76
67
 
@@ -105,7 +96,7 @@ def sample_candidates(
105
96
  n_per_stratum: int,
106
97
  rng: random.Random,
107
98
  ) -> list[SampleCandidate]:
108
- """Stratify by (day x group), then sample per stratum, then flatten.
99
+ """Stratify by (day x source), then sample per stratum, then flatten.
109
100
 
110
101
  Predictable cost across runs: at most ``n_per_stratum * num_strata``
111
102
  items returned, regardless of traffic volume.
@@ -119,7 +110,7 @@ def sample_candidates(
119
110
  Returns:
120
111
  Flattened sampled list.
121
112
  """
122
- strata = stratify_by_day_and_group(candidates)
113
+ strata = stratify_by_day_and_source(candidates)
123
114
  sampled: list[SampleCandidate] = []
124
115
  for stratum_candidates in strata.values():
125
116
  sampled.extend(sample_per_stratum(stratum_candidates, n_per_stratum, rng))