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
@@ -5,8 +5,9 @@ from typing import Any
5
5
 
6
6
  from pydantic import BaseModel, ConfigDict, Field, model_validator
7
7
 
8
- from reflexio.models.api_schema.domain.entities import Interaction
8
+ from reflexio.models.api_schema.domain.entities import Interaction, UserPlaybook
9
9
  from reflexio.models.api_schema.internal_schema import RequestInteractionDataModel
10
+ from reflexio.models.structured_output import StrictStructuredOutput
10
11
  from reflexio.server.prompt.prompt_manager import PromptManager
11
12
  from reflexio.server.services.playbook.playbook_service_constants import (
12
13
  PlaybookServiceConstants,
@@ -21,6 +22,35 @@ from reflexio.server.services.service_utils import (
21
22
 
22
23
  logger = logging.getLogger(__name__)
23
24
 
25
+
26
+ def _normalized_playbook_key(playbook: UserPlaybook) -> tuple[str, str]:
27
+ """Return the same-batch duplicate key for a user playbook."""
28
+ return (
29
+ (playbook.trigger or "").strip().casefold(),
30
+ playbook.content.strip().casefold(),
31
+ )
32
+
33
+
34
+ def dedupe_and_drop_empty(playbooks: list[UserPlaybook]) -> list[UserPlaybook]:
35
+ """Drop empty user playbooks and collapse exact same-batch duplicates.
36
+
37
+ This is intentionally deterministic and local to one extraction batch. The
38
+ LLM-backed consolidator still handles semantic deduplication against stored
39
+ rows when its feature flag is enabled.
40
+ """
41
+ deduped: list[UserPlaybook] = []
42
+ seen: set[tuple[str, str]] = set()
43
+ for playbook in playbooks:
44
+ if not playbook.content.strip():
45
+ continue
46
+ key = _normalized_playbook_key(playbook)
47
+ if key in seen:
48
+ continue
49
+ seen.add(key)
50
+ deduped.append(playbook)
51
+ return deduped
52
+
53
+
24
54
  # ===============================
25
55
  # Pydantic classes for playbook_extraction_main prompt output schema
26
56
  # ===============================
@@ -84,7 +114,7 @@ class StructuredPlaybookContent(BaseModel):
84
114
  return bool(self.content and self.content.strip())
85
115
 
86
116
 
87
- class StructuredPlaybookList(BaseModel):
117
+ class StructuredPlaybookList(StrictStructuredOutput):
88
118
  """
89
119
  Wrapper schema for extracting zero or more playbook entries in a single LLM call.
90
120
 
@@ -105,7 +135,7 @@ class StructuredPlaybookList(BaseModel):
105
135
  )
106
136
 
107
137
 
108
- class PlaybookAggregationOutput(BaseModel):
138
+ class PlaybookAggregationOutput(StrictStructuredOutput):
109
139
  """
110
140
  Output schema for the playbook_aggregation prompt.
111
141
 
@@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
8
8
  if TYPE_CHECKING:
9
9
  from reflexio.server.api_endpoints.request_context import RequestContext
10
10
  from reflexio.server.llm.litellm_client import LiteLLMClient
11
+ from reflexio.server.services.storage.storage_base import BaseStorage
11
12
 
12
13
  from reflexio.models.api_schema.internal_schema import RequestInteractionDataModel
13
14
  from reflexio.models.api_schema.service_schemas import (
@@ -21,13 +22,17 @@ from reflexio.models.api_schema.service_schemas import (
21
22
  UserPlaybook,
22
23
  )
23
24
  from reflexio.models.config_schema import PlaybookConfig
25
+ from reflexio.server.extensions import get_service
24
26
  from reflexio.server.operation_limiter import run_with_operation_limit
25
27
  from reflexio.server.services.base_generation_service import (
26
28
  BaseGenerationService,
27
29
  StatusChangeOperation,
28
30
  )
29
- from reflexio.server.services.playbook.playbook_aggregator import PlaybookAggregator
30
- from reflexio.server.services.playbook.playbook_extractor import PlaybookExtractor
31
+ from reflexio.server.services.playbook.aggregation_prompt_processing import (
32
+ AGGREGATION_PROMPT_PROCESSOR,
33
+ )
34
+ from reflexio.server.services.playbook.components.aggregator import PlaybookAggregator
35
+ from reflexio.server.services.playbook.components.extractor import PlaybookExtractor
31
36
  from reflexio.server.services.playbook.playbook_service_constants import (
32
37
  PlaybookServiceConstants,
33
38
  )
@@ -45,6 +50,33 @@ from reflexio.server.services.service_utils import (
45
50
  logger = logging.getLogger(__name__)
46
51
 
47
52
 
53
+ def read_user_playbook_as_of_for_learning(
54
+ storage: BaseStorage, *, user_playbook_id: int, served_at: int
55
+ ) -> UserPlaybook | None:
56
+ """Return the exact cited playbook row iff its body is still attributable.
57
+
58
+ This is the strict learning-time read path: it dereferences ONLY the cited
59
+ id, never follows merged/superseded pointers to a current survivor. When the
60
+ exact row body is unavailable or was revised in-place after ``served_at``,
61
+ the row is unattributable and the helper returns ``None``.
62
+ """
63
+ playbook = storage.get_user_playbook_by_id(
64
+ user_playbook_id, include_tombstones=True
65
+ )
66
+ if playbook is None:
67
+ return None
68
+ if playbook.created_at >= served_at:
69
+ return None
70
+ if not playbook.content.strip():
71
+ return None
72
+ events = storage.get_lineage_events(
73
+ entity_type="user_playbook", entity_id=str(user_playbook_id)
74
+ )
75
+ if any(event.op == "revise" and event.created_at >= served_at for event in events):
76
+ return None
77
+ return playbook
78
+
79
+
48
80
  @dataclass
49
81
  class PlaybookGenerationServiceConfig:
50
82
  """Runtime configuration for playbook generation service shared across all extractors.
@@ -84,6 +116,9 @@ class PlaybookGenerationService(
84
116
  Runs the configured PlaybookExtractor for each generation request.
85
117
  """
86
118
 
119
+ # Playbook generation produces learnings — opt in to ② Learning billing.
120
+ EMITS_LEARNING_BILLING: bool = True
121
+
87
122
  def __init__(
88
123
  self,
89
124
  llm_client: LiteLLMClient,
@@ -257,12 +292,19 @@ class PlaybookGenerationService(
257
292
 
258
293
  def _finalize_extracted_items(self, all_playbooks: list[UserPlaybook]) -> None:
259
294
  """Deduplicate, persist, and aggregate extracted user playbook items."""
295
+ from reflexio.server.services.playbook.playbook_service_utils import (
296
+ dedupe_and_drop_empty,
297
+ )
298
+
299
+ all_playbooks = dedupe_and_drop_empty(all_playbooks)
300
+
260
301
  # Deduplicate against existing entries in DB when deduplicator is enabled
261
302
  existing_ids_to_delete: list[int] = []
303
+ merge_groups: list[tuple[int, list[int]]] = []
262
304
  from reflexio.server.site_var.feature_flags import is_deduplicator_enabled
263
305
 
264
306
  if is_deduplicator_enabled(self.org_id):
265
- from reflexio.server.services.playbook.playbook_consolidator import (
307
+ from reflexio.server.services.playbook.components.consolidator import (
266
308
  PlaybookConsolidator,
267
309
  )
268
310
 
@@ -276,7 +318,11 @@ class PlaybookGenerationService(
276
318
  llm_client=self.client,
277
319
  dedup_config=dedup_config,
278
320
  )
279
- deduplicated_playbooks, existing_ids_to_delete = consolidator.deduplicate(
321
+ (
322
+ deduplicated_playbooks,
323
+ existing_ids_to_delete,
324
+ merge_groups,
325
+ ) = consolidator.deduplicate(
280
326
  [all_playbooks],
281
327
  self.service_config.request_id, # type: ignore[reportOptionalMemberAccess]
282
328
  self.service_config.agent_version, # type: ignore[reportOptionalMemberAccess]
@@ -308,21 +354,9 @@ class PlaybookGenerationService(
308
354
  try:
309
355
  self.storage.save_user_playbooks(all_playbooks) # type: ignore[reportOptionalMemberAccess]
310
356
  self._enqueue_user_playbook_optimization(all_playbooks)
311
-
312
- # Delete superseded existing entries only after save succeeds
313
- if existing_ids_to_delete:
314
- try:
315
- deleted_count = self.storage.delete_user_playbooks_by_ids( # type: ignore[reportOptionalMemberAccess]
316
- existing_ids_to_delete
317
- )
318
- logger.info(
319
- "Deleted %d superseded existing entries", deleted_count
320
- )
321
- except Exception as e:
322
- logger.error(
323
- "Failed to delete superseded existing entries: %s",
324
- str(e),
325
- )
357
+ self._apply_consolidation_lineage(
358
+ all_playbooks, merge_groups, existing_ids_to_delete
359
+ )
326
360
  except Exception as e:
327
361
  logger.error(
328
362
  "Failed to save %s results for request id: %s due to %s, exception type: %s",
@@ -331,12 +365,73 @@ class PlaybookGenerationService(
331
365
  str(e),
332
366
  type(e).__name__,
333
367
  )
368
+ raise
334
369
 
335
370
  # Trigger playbook aggregation
336
371
  if not self.output_pending_status and not self.skip_aggregation:
337
372
  logger.info("Trigger playbook aggregation")
338
373
  self._trigger_playbook_aggregation()
339
374
 
375
+ def _apply_consolidation_lineage(
376
+ self,
377
+ saved_playbooks: list[UserPlaybook],
378
+ merge_groups: list[tuple[int, list[int]]],
379
+ existing_ids_to_delete: list[int],
380
+ ) -> None:
381
+ """Materialize consolidation merges as lineage tombstones.
382
+
383
+ Runs AFTER ``save_user_playbooks`` has assigned survivor ids. For each
384
+ ``unify`` merge group, routes the sources atomically through
385
+ ``storage.merge_records`` so each becomes a MERGED tombstone pointing at
386
+ its survivor with a ``merge`` lineage event (``resolve_current`` then
387
+ follows the pointer). Any archived id NOT covered by a merge group
388
+ (e.g. a ``differentiate`` split source, which has no single survivor) is
389
+ soft-superseded in place so the dead source content stays auditable for
390
+ point-in-time attribution reads.
391
+
392
+ Args:
393
+ saved_playbooks: The just-persisted entries (survivor ids assigned).
394
+ merge_groups: ``(survivor_index, source_existing_ids)`` per merge.
395
+ existing_ids_to_delete: ALL archived ids (merge sources + leftovers).
396
+ """
397
+ from reflexio.models.api_schema.domain.entities import LineageContext
398
+
399
+ merged_source_ids: set[int] = set()
400
+ for survivor_idx, source_ids in merge_groups:
401
+ survivor_id = saved_playbooks[survivor_idx].user_playbook_id
402
+ merged_source_ids.update(source_ids)
403
+ self.storage.merge_records( # type: ignore[reportOptionalMemberAccess]
404
+ entity_type="user_playbook",
405
+ survivor_id=str(survivor_id),
406
+ source_ids=[str(s) for s in source_ids],
407
+ context=LineageContext(
408
+ op_kind="merge",
409
+ actor="consolidator",
410
+ source_ids=[str(s) for s in source_ids],
411
+ reason="dedup-merge",
412
+ request_id=self.service_config.request_id, # type: ignore[reportOptionalMemberAccess]
413
+ ),
414
+ )
415
+
416
+ # Leftover archives not covered by any merge group (e.g. differentiate
417
+ # split sources) have no single survivor, so preserve them as dead-source
418
+ # tombstones instead of hard-deleting them.
419
+ leftover_ids = [
420
+ pid for pid in existing_ids_to_delete if pid not in merged_source_ids
421
+ ]
422
+ if leftover_ids:
423
+ try:
424
+ superseded_count = self.storage.supersede_user_playbooks_by_ids( # type: ignore[reportOptionalMemberAccess]
425
+ leftover_ids,
426
+ self.service_config.request_id, # type: ignore[reportOptionalMemberAccess]
427
+ )
428
+ logger.info(
429
+ "Superseded %d split-source existing entries", superseded_count
430
+ )
431
+ except Exception:
432
+ logger.exception("Failed to supersede split-source existing entries")
433
+ raise
434
+
340
435
  def _enqueue_user_playbook_optimization(
341
436
  self, saved_playbooks: list[UserPlaybook]
342
437
  ) -> None:
@@ -446,10 +541,17 @@ class PlaybookGenerationService(
446
541
  )
447
542
 
448
543
  # Initialize and run aggregator (synchronous)
544
+ aggregation_prompt_processor = get_service(AGGREGATION_PROMPT_PROCESSOR)
545
+ aggregator_kwargs = {}
546
+ if aggregation_prompt_processor is not None:
547
+ aggregator_kwargs["aggregation_prompt_processor"] = (
548
+ aggregation_prompt_processor
549
+ )
449
550
  aggregator = PlaybookAggregator(
450
551
  llm_client=self.client,
451
552
  request_context=self.request_context,
452
553
  agent_version=self.service_config.agent_version, # type: ignore[reportOptionalMemberAccess]
554
+ **aggregator_kwargs,
453
555
  )
454
556
  try:
455
557
  run_with_operation_limit(
@@ -0,0 +1,10 @@
1
+ # playbook_optimizer
2
+
3
+ GEPA-driven playbook content optimizer.
4
+
5
+ - `optimizer.py` orchestrates one optimization run and persists candidates, evaluations, events, and optional successor playbooks.
6
+ - `scheduler.py` owns deferred optimization scheduling.
7
+ - `models.py` owns optimizer-local data shapes.
8
+ - `judge.py`, `rollout.py`, `gepa_adapter.py`, `assistant_webhook.py`, and `scenario_resolver.py` are mature implementation units and intentionally remain at the package root.
9
+
10
+ Do not introduce a `components/` package without a separate design that proves the dependency direction is clearer after the move.
@@ -13,6 +13,7 @@ from typing import Any, Literal
13
13
  from pydantic import BaseModel, Field
14
14
 
15
15
  from reflexio.models.api_schema.domain import Interaction
16
+ from reflexio.models.structured_output import StrictStructuredOutput
16
17
 
17
18
 
18
19
  class ChatMessage(BaseModel):
@@ -58,7 +59,7 @@ class JudgeASI(BaseModel):
58
59
  recommended_mutation: str | list[str] = ""
59
60
 
60
61
 
61
- class JudgeOutput(BaseModel):
62
+ class JudgeOutput(StrictStructuredOutput):
62
63
  verdict: Literal["candidate", "incumbent", "tie"]
63
64
  score: float = Field(ge=0.0, le=1.0)
64
65
  likert: int = Field(ge=1, le=5)
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import logging
5
+ import os
6
+ from pathlib import Path
5
7
  from typing import Any, Literal, cast
6
8
 
7
9
  from pydantic import BaseModel
@@ -14,6 +16,7 @@ from reflexio.models.api_schema.domain import (
14
16
  PlaybookStatus,
15
17
  UserPlaybook,
16
18
  )
19
+ from reflexio.models.api_schema.domain.entities import LineageContext
17
20
  from reflexio.models.config_schema import PlaybookOptimizerConfig
18
21
  from reflexio.server.api_endpoints.request_context import RequestContext
19
22
  from reflexio.server.llm.litellm_client import LiteLLMClient
@@ -28,6 +31,22 @@ from .scenario_resolver import ScenarioResolver
28
31
 
29
32
  logger = logging.getLogger(__name__)
30
33
 
34
+ # Prefix used when constructing the run-scoped lineage request_id from a job id.
35
+ # Shared with tests so the format is defined in one place.
36
+ _OPTIMIZER_RUN_ID_PREFIX = "optjob_"
37
+
38
+
39
+ def optimizer_run_request_id(job_id: int) -> str:
40
+ """Return the lineage request_id for a playbook optimizer run.
41
+
42
+ Args:
43
+ job_id (int): The ``PlaybookOptimizationJob.job_id`` for this run.
44
+
45
+ Returns:
46
+ str: A non-empty, job-scoped request id of the form ``optjob_<job_id>``.
47
+ """
48
+ return f"{_OPTIMIZER_RUN_ID_PREFIX}{job_id}"
49
+
31
50
 
32
51
  class PlaybookOptimizationTarget(BaseModel):
33
52
  """A single playbook (agent or user) the optimizer should try to improve."""
@@ -126,6 +145,7 @@ class PlaybookOptimizer:
126
145
  metadata_json=json.dumps(split_metadata, ensure_ascii=False),
127
146
  )
128
147
  )
148
+ run_request_id = optimizer_run_request_id(job.job_id)
129
149
  logger.info(
130
150
  "event=playbook_optimization_start job_id=%d candidate_id=none "
131
151
  "target_kind=%s target_id=%d "
@@ -248,7 +268,9 @@ class PlaybookOptimizer:
248
268
  )
249
269
  return "completed"
250
270
 
251
- successor_id = self._commit_if_allowed(target, incumbent, best_content, config)
271
+ successor_id = self._commit_if_allowed(
272
+ target, incumbent, best_content, config, run_request_id
273
+ )
252
274
  logger.info(
253
275
  "event=playbook_optimization_committed job_id=%d candidate_id=%d "
254
276
  "successor_target_id=%s best_score=%.3f",
@@ -331,6 +353,13 @@ class PlaybookOptimizer:
331
353
  backoff_base_s=config.webhook_backoff_base_seconds,
332
354
  )
333
355
  if config.assistant_script_path:
356
+ if not _is_executable_file(config.assistant_script_path):
357
+ logger.warning(
358
+ "Skipping playbook optimization: assistant_script_path is not "
359
+ "an executable file path=%s",
360
+ config.assistant_script_path,
361
+ )
362
+ return None
334
363
  return LocalScriptAssistant(
335
364
  script_path=config.assistant_script_path,
336
365
  script_args=config.assistant_script_args,
@@ -415,6 +444,7 @@ class PlaybookOptimizer:
415
444
  incumbent: AgentPlaybook,
416
445
  best_content: str,
417
446
  config: PlaybookOptimizerConfig,
447
+ run_request_id: str,
418
448
  ) -> int | None:
419
449
  # The optimize() entrypoint already gates on _can_adopt_winner before
420
450
  # creating the job, but check again so this stays correct if called
@@ -432,49 +462,39 @@ class PlaybookOptimizer:
432
462
  or current.playbook_status != PlaybookStatus.PENDING
433
463
  ):
434
464
  return None
435
- self.storage.archive_agent_playbooks_by_ids([target.target_id])
436
- successor = incumbent.model_copy(
437
- update={
438
- "agent_playbook_id": 0,
439
- "content": best_content,
440
- "status": None,
441
- "playbook_status": PlaybookStatus.PENDING,
442
- "playbook_metadata": _append_optimizer_metadata(
443
- incumbent.playbook_metadata, target.target_id
444
- ),
445
- }
465
+ metadata = _append_optimizer_metadata(
466
+ incumbent.playbook_metadata, target.target_id
467
+ )
468
+ successor_id = _supersede_agent_playbook(
469
+ self.storage,
470
+ incumbent,
471
+ best_content,
472
+ "playbook_optimizer",
473
+ playbook_metadata=metadata,
474
+ request_id=run_request_id,
446
475
  )
447
- saved = self.storage.save_agent_playbooks([successor])
448
- if saved and saved[0].agent_playbook_id:
476
+ if successor_id is not None:
449
477
  self.storage.set_source_windows_for_agent_playbook(
450
- saved[0].agent_playbook_id, source_windows
478
+ successor_id, source_windows
451
479
  )
452
- return saved[0].agent_playbook_id
453
- return None
480
+ return successor_id
454
481
  current_user = self.storage.get_user_playbook_by_id(target.target_id)
455
482
  if current_user is None or current_user.status is not None:
456
483
  return None
457
484
  if current_user.user_id is None:
458
485
  return None
459
- archived = self.storage.archive_user_playbook_by_id(
460
- current_user.user_id, current_user.user_playbook_id
461
- )
462
- if not archived:
463
- return None
464
486
  # The optimizer can legitimately flip framing (positive guidance ->
465
487
  # negative anti-pattern or vice versa). Orientation lives entirely in
466
488
  # the rule wording, so writing ``best_content`` is sufficient — there
467
489
  # is no derived polarity label or separate polarity field to keep in
468
490
  # sync.
469
- successor_user = current_user.model_copy(
470
- update={
471
- "user_playbook_id": 0,
472
- "content": best_content,
473
- "status": None,
474
- }
491
+ return _supersede_user_playbook(
492
+ self.storage,
493
+ current_user,
494
+ best_content,
495
+ "playbook_optimizer",
496
+ request_id=run_request_id,
475
497
  )
476
- self.storage.save_user_playbooks([successor_user])
477
- return successor_user.user_playbook_id or None
478
498
 
479
499
 
480
500
  def _agent_like_playbook(playbook: UserPlaybook) -> AgentPlaybook:
@@ -506,6 +526,129 @@ def _append_optimizer_metadata(existing: str, predecessor_id: int) -> str:
506
526
  return f"{existing}; {suffix}"
507
527
 
508
528
 
529
+ def _supersede_user_playbook(
530
+ storage: Any,
531
+ incumbent: UserPlaybook,
532
+ best_content: str,
533
+ source: str,
534
+ *,
535
+ request_id: str,
536
+ ) -> int | None:
537
+ """Insert a user-playbook successor then atomically supersede the incumbent.
538
+
539
+ Returns the new ``user_playbook_id`` on success, or ``None`` when the
540
+ incumbent is no longer CURRENT (lost race / already superseded).
541
+
542
+ Args:
543
+ storage: A storage instance implementing ``save_user_playbooks``,
544
+ ``supersede_record``, and ``delete_user_playbooks_by_ids``.
545
+ incumbent: The current user playbook to replace.
546
+ best_content: Content for the successor playbook.
547
+ source: Provenance label written to the lineage event actor field.
548
+ request_id: Run-scoped correlation id for the lineage event. Must be
549
+ non-empty; use the job-derived id from the calling optimizer run.
550
+
551
+ Returns:
552
+ int | None: ``user_playbook_id`` of the successor, or ``None`` if the
553
+ incumbent was not CURRENT.
554
+
555
+ Raises:
556
+ ValueError: If ``request_id`` is empty or None.
557
+ """
558
+ if not request_id:
559
+ raise ValueError(
560
+ "_supersede_user_playbook: request_id must be non-empty (run-correlation id)"
561
+ )
562
+ successor = incumbent.model_copy(
563
+ update={"user_playbook_id": 0, "content": best_content, "status": None}
564
+ )
565
+ storage.save_user_playbooks([successor])
566
+ ctx = LineageContext(
567
+ op_kind="revise",
568
+ actor=source,
569
+ request_id=request_id,
570
+ )
571
+ ok = storage.supersede_record(
572
+ entity_type="user_playbook",
573
+ incumbent_id=str(incumbent.user_playbook_id),
574
+ successor_id=str(successor.user_playbook_id),
575
+ context=ctx,
576
+ )
577
+ if not ok:
578
+ # Lost CAS: remove the never-live successor without auditing it as erasure.
579
+ storage.delete_user_playbooks_by_ids(
580
+ [successor.user_playbook_id], emit_hard_delete=False
581
+ )
582
+ return None
583
+ return successor.user_playbook_id
584
+
585
+
586
+ def _supersede_agent_playbook(
587
+ storage: Any,
588
+ incumbent: AgentPlaybook,
589
+ best_content: str,
590
+ source: str,
591
+ playbook_metadata: str = "",
592
+ *,
593
+ request_id: str,
594
+ ) -> int | None:
595
+ """Insert an agent-playbook successor then atomically supersede the incumbent.
596
+
597
+ Returns the new ``agent_playbook_id`` on success, or ``None`` when the
598
+ incumbent is no longer CURRENT.
599
+
600
+ Args:
601
+ storage: A storage instance implementing ``save_agent_playbooks``,
602
+ ``supersede_record``, and ``delete_agent_playbooks_by_ids``.
603
+ incumbent: The current agent playbook to replace.
604
+ best_content: Content for the successor playbook.
605
+ source: Provenance label written to the lineage event actor field.
606
+ playbook_metadata: Optional metadata string for the successor row.
607
+ request_id: Run-scoped correlation id for the lineage event. Must be
608
+ non-empty; use the job-derived id from the calling optimizer run.
609
+
610
+ Returns:
611
+ int | None: ``agent_playbook_id`` of the successor, or ``None`` if the
612
+ incumbent was not CURRENT.
613
+
614
+ Raises:
615
+ ValueError: If ``request_id`` is empty or None.
616
+ """
617
+ if not request_id:
618
+ raise ValueError(
619
+ "_supersede_agent_playbook: request_id must be non-empty (run-correlation id)"
620
+ )
621
+ successor = incumbent.model_copy(
622
+ update={
623
+ "agent_playbook_id": 0,
624
+ "content": best_content,
625
+ "status": None,
626
+ "playbook_status": PlaybookStatus.PENDING,
627
+ "playbook_metadata": playbook_metadata,
628
+ }
629
+ )
630
+ saved = storage.save_agent_playbooks([successor])
631
+ if not saved or not saved[0].agent_playbook_id:
632
+ return None
633
+ successor_id = saved[0].agent_playbook_id
634
+ ctx = LineageContext(
635
+ op_kind="revise",
636
+ actor=source,
637
+ request_id=request_id,
638
+ )
639
+ ok = storage.supersede_record(
640
+ entity_type="agent_playbook",
641
+ incumbent_id=str(incumbent.agent_playbook_id),
642
+ successor_id=str(successor_id),
643
+ context=ctx,
644
+ )
645
+ if not ok:
646
+ # Lost CAS: remove the never-live successor without auditing it as erasure.
647
+ storage.delete_agent_playbooks_by_ids([successor_id], emit_hard_delete=False)
648
+ return None
649
+ return successor_id
650
+
651
+
509
652
  class _GEPAStorageCallback:
510
653
  def __init__(self, storage: Any, job_id: int) -> None:
511
654
  self.storage = storage
@@ -627,6 +770,11 @@ def _split_metadata(
627
770
  }
628
771
 
629
772
 
773
+ def _is_executable_file(path: str) -> bool:
774
+ candidate = Path(path)
775
+ return candidate.is_file() and os.access(candidate, os.X_OK)
776
+
777
+
630
778
  def _result_metadata(result: Any, split_metadata: dict[str, Any]) -> dict[str, Any]:
631
779
  metadata = result.to_dict()
632
780
  if not isinstance(metadata, dict):
@@ -0,0 +1,9 @@
1
+ # pre_retrieval
2
+
3
+ Compact search-preparation capability.
4
+
5
+ - `__init__.py` is the public import surface for query reformulation and document expansion.
6
+ - `_query_reformulator.py` rewrites user queries and can run a caller-provided search function.
7
+ - `_document_expander.py` enriches stored documents with related terms before indexing.
8
+
9
+ This package intentionally does not use `components/`: both implementation files are already focused, and storage adapters import the package-level public surface.
@@ -0,0 +1,17 @@
1
+ """Profile service components."""
2
+
3
+ from reflexio.server.services.profile.components.consolidator import (
4
+ ProfileConsolidator,
5
+ ProfileDeduplicationOutput,
6
+ ProfileDeletionDirective,
7
+ ProfileDuplicateGroup,
8
+ )
9
+ from reflexio.server.services.profile.components.extractor import ProfileExtractor
10
+
11
+ __all__ = [
12
+ "ProfileConsolidator",
13
+ "ProfileDeduplicationOutput",
14
+ "ProfileDeletionDirective",
15
+ "ProfileDuplicateGroup",
16
+ "ProfileExtractor",
17
+ ]
@@ -1,5 +1,5 @@
1
1
  """
2
- Profile deduplication service that merges duplicate profiles from multiple extractors
2
+ Profile consolidation service that merges duplicate profiles from multiple extractors
3
3
  and against existing profiles in the database using hybrid search and LLM.
4
4
  """
5
5
 
@@ -13,6 +13,7 @@ from pydantic import BaseModel, ConfigDict, Field
13
13
 
14
14
  from reflexio.models.api_schema.retriever_schema import SearchUserProfileRequest
15
15
  from reflexio.models.api_schema.service_schemas import Status, UserProfile
16
+ from reflexio.models.structured_output import StrictStructuredOutput
16
17
  from reflexio.server.api_endpoints.request_context import RequestContext
17
18
  from reflexio.server.llm.litellm_client import LiteLLMClient
18
19
  from reflexio.server.services.deduplication_utils import (
@@ -130,7 +131,7 @@ class ProfileDeletionDirective(BaseModel):
130
131
  )
131
132
 
132
133
 
133
- class ProfileDeduplicationOutput(BaseModel):
134
+ class ProfileDeduplicationOutput(StrictStructuredOutput):
134
135
  """
135
136
  Output schema for profile deduplication with NEW/EXISTING format.
136
137
 
@@ -165,9 +166,9 @@ class ProfileDeduplicationOutput(BaseModel):
165
166
  )
166
167
 
167
168
 
168
- class ProfileDeduplicator(BaseDeduplicator):
169
+ class ProfileConsolidator(BaseDeduplicator):
169
170
  """
170
- Deduplicates new profiles against each other and against existing profiles
171
+ Consolidates new profiles against each other and against existing profiles
171
172
  in the database using hybrid search (vector + FTS) and LLM-based merging.
172
173
 
173
174
  Follows the same pattern as PlaybookConsolidator.
@@ -182,12 +183,12 @@ class ProfileDeduplicator(BaseDeduplicator):
182
183
  output_pending_status: bool = False,
183
184
  ):
184
185
  """
185
- Initialize the profile deduplicator.
186
+ Initialize the profile consolidator.
186
187
 
187
188
  Args:
188
189
  request_context: Request context with storage and prompt manager
189
190
  llm_client: Unified LLM client for LLM calls
190
- output_pending_status: When True (rerun preview mode), the deduplicator
191
+ output_pending_status: When True (rerun preview mode), the consolidator
191
192
  searches against existing PENDING profiles instead of CURRENT
192
193
  profiles. This makes rerun idempotent against pending state and
193
194
  leaves the user's current profile set untouched. When False