claude-smart 0.2.44 → 0.2.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +34 -5
  3. package/bin/claude-smart.js +295 -5
  4. package/package.json +22 -3
  5. package/plugin/.claude-plugin/plugin.json +4 -2
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +22 -2
  8. package/plugin/commands/clear-all.md +1 -0
  9. package/plugin/commands/dashboard.md +1 -0
  10. package/plugin/commands/learn.md +1 -0
  11. package/plugin/commands/restart.md +1 -0
  12. package/plugin/commands/show.md +1 -0
  13. package/plugin/dashboard/app/configure/env/page.tsx +40 -14
  14. package/plugin/dashboard/app/configure/server/page.tsx +51 -1
  15. package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
  16. package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
  17. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
  18. package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
  19. package/plugin/dashboard/lib/reflexio-client.ts +16 -0
  20. package/plugin/dashboard/lib/status.ts +10 -3
  21. package/plugin/dashboard/lib/types.ts +18 -6
  22. package/plugin/hooks/codex-hooks.json +0 -1
  23. package/plugin/opencode/assistant-buffer.ts +108 -0
  24. package/plugin/opencode/dist/assistant-buffer.js +96 -0
  25. package/plugin/opencode/dist/internal.js +12 -0
  26. package/plugin/opencode/dist/payload.js +85 -0
  27. package/plugin/opencode/dist/server.mjs +158 -0
  28. package/plugin/opencode/internal.ts +18 -0
  29. package/plugin/opencode/package.json +3 -0
  30. package/plugin/opencode/payload.ts +90 -0
  31. package/plugin/opencode/server.mts +174 -0
  32. package/plugin/opencode/tsconfig.json +13 -0
  33. package/plugin/pyproject.toml +20 -3
  34. package/plugin/scripts/_lib.sh +77 -29
  35. package/plugin/scripts/backend-service.sh +27 -5
  36. package/plugin/scripts/dashboard-service.sh +7 -5
  37. package/plugin/scripts/ensure-plugin-root.sh +4 -4
  38. package/plugin/scripts/hook_entry.sh +3 -3
  39. package/plugin/scripts/opencode-claude-compat +4 -0
  40. package/plugin/scripts/opencode-claude-compat.cmd +3 -0
  41. package/plugin/scripts/opencode-claude-compat.js +225 -0
  42. package/plugin/scripts/smart-install.sh +10 -8
  43. package/plugin/src/README.md +1 -1
  44. package/plugin/src/claude_smart/cli.py +304 -6
  45. package/plugin/src/claude_smart/env_config.py +12 -4
  46. package/plugin/src/claude_smart/events/session_start.py +26 -7
  47. package/plugin/src/claude_smart/events/stop.py +1 -1
  48. package/plugin/src/claude_smart/ids.py +1 -1
  49. package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
  50. package/plugin/src/claude_smart/runtime.py +7 -1
  51. package/plugin/uv.lock +1 -1
  52. package/plugin/vendor/reflexio/.env.example +54 -30
  53. package/plugin/vendor/reflexio/README.md +14 -8
  54. package/plugin/vendor/reflexio/pyproject.toml +13 -1
  55. package/plugin/vendor/reflexio/reflexio/README.md +1 -0
  56. package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
  57. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
  58. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
  59. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
  60. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +210 -50
  63. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
  64. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
  65. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
  66. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
  67. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
  68. package/plugin/vendor/reflexio/reflexio/client/client.py +57 -13
  69. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
  70. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
  71. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
  72. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
  142. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +238 -10
  143. package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
  144. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +16 -5
  145. package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
  146. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +198 -6
  147. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
  148. package/plugin/vendor/reflexio/reflexio/lib/_search.py +29 -20
  149. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +2 -1
  150. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
  151. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
  152. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
  153. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
  154. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
  155. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +52 -0
  156. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
  157. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
  158. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +88 -7
  159. package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
  160. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
  161. package/plugin/vendor/reflexio/reflexio/server/README.md +45 -32
  162. package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
  163. package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
  164. package/plugin/vendor/reflexio/reflexio/server/_auth.py +65 -2
  165. package/plugin/vendor/reflexio/reflexio/server/api.py +734 -85
  166. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
  167. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +3 -1
  168. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
  169. package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
  170. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
  171. package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
  172. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
  173. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
  174. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
  175. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
  176. package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
  177. package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
  178. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
  179. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
  180. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
  181. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
  182. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
  183. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
  184. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
  185. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
  186. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
  187. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
  188. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +266 -0
  189. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
  190. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
  191. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
  192. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
  193. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
  194. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
  195. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
  196. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
  197. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
  198. package/plugin/vendor/reflexio/reflexio/server/services/README.md +26 -11
  199. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
  200. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
  201. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
  202. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
  203. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
  204. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
  205. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
  206. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
  207. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
  208. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
  209. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
  210. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
  211. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
  212. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
  213. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
  214. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
  215. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
  216. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
  217. package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
  218. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
  219. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
  220. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
  221. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +39 -16
  222. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
  223. package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
  224. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +193 -0
  225. package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
  226. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
  227. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +11 -12
  228. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
  229. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +316 -178
  230. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
  231. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
  232. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
  233. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
  234. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
  235. package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +84 -0
  236. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
  237. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
  238. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
  239. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
  240. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
  241. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
  242. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +30 -22
  243. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
  244. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
  245. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
  246. package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
  247. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
  248. package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
  249. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +83 -6
  250. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
  251. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
  252. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
  253. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +27 -0
  254. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
  255. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
  256. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
  257. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +642 -197
  258. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +61 -104
  259. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +628 -0
  260. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
  261. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +978 -184
  262. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +519 -128
  263. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +77 -3
  264. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
  265. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
  266. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +131 -20
  267. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
  268. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
  269. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +36 -53
  270. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
  271. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
  272. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +243 -7
  273. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +121 -1
  274. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +60 -0
  275. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
  277. package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
  278. package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
  279. package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
  280. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
  281. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +229 -32
  282. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
  283. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
  284. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
  285. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
  286. package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
  287. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
  288. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
  289. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
  290. package/scripts/setup-claude-smart.sh +8 -3
  291. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
  292. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
  293. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
  294. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
  295. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
  296. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
  297. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
  298. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
  299. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
  300. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
  301. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
  302. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
  303. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
  304. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
  305. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
  306. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
  307. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
  308. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
  309. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
  310. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
  311. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
  312. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
  313. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
  314. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
  315. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
  316. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
  317. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
  318. /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
  319. /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
  320. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
  321. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
  322. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
  323. /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
@@ -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 (
@@ -26,8 +27,8 @@ from reflexio.server.services.base_generation_service import (
26
27
  BaseGenerationService,
27
28
  StatusChangeOperation,
28
29
  )
29
- from reflexio.server.services.playbook.playbook_aggregator import PlaybookAggregator
30
- from reflexio.server.services.playbook.playbook_extractor import PlaybookExtractor
30
+ from reflexio.server.services.playbook.components.aggregator import PlaybookAggregator
31
+ from reflexio.server.services.playbook.components.extractor import PlaybookExtractor
31
32
  from reflexio.server.services.playbook.playbook_service_constants import (
32
33
  PlaybookServiceConstants,
33
34
  )
@@ -45,6 +46,33 @@ from reflexio.server.services.service_utils import (
45
46
  logger = logging.getLogger(__name__)
46
47
 
47
48
 
49
+ def read_user_playbook_as_of_for_learning(
50
+ storage: BaseStorage, *, user_playbook_id: int, served_at: int
51
+ ) -> UserPlaybook | None:
52
+ """Return the exact cited playbook row iff its body is still attributable.
53
+
54
+ This is the strict learning-time read path: it dereferences ONLY the cited
55
+ id, never follows merged/superseded pointers to a current survivor. When the
56
+ exact row body is unavailable or was revised in-place after ``served_at``,
57
+ the row is unattributable and the helper returns ``None``.
58
+ """
59
+ playbook = storage.get_user_playbook_by_id(
60
+ user_playbook_id, include_tombstones=True
61
+ )
62
+ if playbook is None:
63
+ return None
64
+ if playbook.created_at >= served_at:
65
+ return None
66
+ if not playbook.content.strip():
67
+ return None
68
+ events = storage.get_lineage_events(
69
+ entity_type="user_playbook", entity_id=str(user_playbook_id)
70
+ )
71
+ if any(event.op == "revise" and event.created_at >= served_at for event in events):
72
+ return None
73
+ return playbook
74
+
75
+
48
76
  @dataclass
49
77
  class PlaybookGenerationServiceConfig:
50
78
  """Runtime configuration for playbook generation service shared across all extractors.
@@ -84,6 +112,9 @@ class PlaybookGenerationService(
84
112
  Runs the configured PlaybookExtractor for each generation request.
85
113
  """
86
114
 
115
+ # Playbook generation produces learnings — opt in to ② Learning billing.
116
+ EMITS_LEARNING_BILLING: bool = True
117
+
87
118
  def __init__(
88
119
  self,
89
120
  llm_client: LiteLLMClient,
@@ -257,12 +288,19 @@ class PlaybookGenerationService(
257
288
 
258
289
  def _finalize_extracted_items(self, all_playbooks: list[UserPlaybook]) -> None:
259
290
  """Deduplicate, persist, and aggregate extracted user playbook items."""
291
+ from reflexio.server.services.playbook.playbook_service_utils import (
292
+ dedupe_and_drop_empty,
293
+ )
294
+
295
+ all_playbooks = dedupe_and_drop_empty(all_playbooks)
296
+
260
297
  # Deduplicate against existing entries in DB when deduplicator is enabled
261
298
  existing_ids_to_delete: list[int] = []
299
+ merge_groups: list[tuple[int, list[int]]] = []
262
300
  from reflexio.server.site_var.feature_flags import is_deduplicator_enabled
263
301
 
264
302
  if is_deduplicator_enabled(self.org_id):
265
- from reflexio.server.services.playbook.playbook_consolidator import (
303
+ from reflexio.server.services.playbook.components.consolidator import (
266
304
  PlaybookConsolidator,
267
305
  )
268
306
 
@@ -276,7 +314,11 @@ class PlaybookGenerationService(
276
314
  llm_client=self.client,
277
315
  dedup_config=dedup_config,
278
316
  )
279
- deduplicated_playbooks, existing_ids_to_delete = consolidator.deduplicate(
317
+ (
318
+ deduplicated_playbooks,
319
+ existing_ids_to_delete,
320
+ merge_groups,
321
+ ) = consolidator.deduplicate(
280
322
  [all_playbooks],
281
323
  self.service_config.request_id, # type: ignore[reportOptionalMemberAccess]
282
324
  self.service_config.agent_version, # type: ignore[reportOptionalMemberAccess]
@@ -308,21 +350,9 @@ class PlaybookGenerationService(
308
350
  try:
309
351
  self.storage.save_user_playbooks(all_playbooks) # type: ignore[reportOptionalMemberAccess]
310
352
  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
- )
353
+ self._apply_consolidation_lineage(
354
+ all_playbooks, merge_groups, existing_ids_to_delete
355
+ )
326
356
  except Exception as e:
327
357
  logger.error(
328
358
  "Failed to save %s results for request id: %s due to %s, exception type: %s",
@@ -331,12 +361,73 @@ class PlaybookGenerationService(
331
361
  str(e),
332
362
  type(e).__name__,
333
363
  )
364
+ raise
334
365
 
335
366
  # Trigger playbook aggregation
336
367
  if not self.output_pending_status and not self.skip_aggregation:
337
368
  logger.info("Trigger playbook aggregation")
338
369
  self._trigger_playbook_aggregation()
339
370
 
371
+ def _apply_consolidation_lineage(
372
+ self,
373
+ saved_playbooks: list[UserPlaybook],
374
+ merge_groups: list[tuple[int, list[int]]],
375
+ existing_ids_to_delete: list[int],
376
+ ) -> None:
377
+ """Materialize consolidation merges as lineage tombstones.
378
+
379
+ Runs AFTER ``save_user_playbooks`` has assigned survivor ids. For each
380
+ ``unify`` merge group, routes the sources atomically through
381
+ ``storage.merge_records`` so each becomes a MERGED tombstone pointing at
382
+ its survivor with a ``merge`` lineage event (``resolve_current`` then
383
+ follows the pointer). Any archived id NOT covered by a merge group
384
+ (e.g. a ``differentiate`` split source, which has no single survivor) is
385
+ soft-superseded in place so the dead source content stays auditable for
386
+ point-in-time attribution reads.
387
+
388
+ Args:
389
+ saved_playbooks: The just-persisted entries (survivor ids assigned).
390
+ merge_groups: ``(survivor_index, source_existing_ids)`` per merge.
391
+ existing_ids_to_delete: ALL archived ids (merge sources + leftovers).
392
+ """
393
+ from reflexio.models.api_schema.domain.entities import LineageContext
394
+
395
+ merged_source_ids: set[int] = set()
396
+ for survivor_idx, source_ids in merge_groups:
397
+ survivor_id = saved_playbooks[survivor_idx].user_playbook_id
398
+ merged_source_ids.update(source_ids)
399
+ self.storage.merge_records( # type: ignore[reportOptionalMemberAccess]
400
+ entity_type="user_playbook",
401
+ survivor_id=str(survivor_id),
402
+ source_ids=[str(s) for s in source_ids],
403
+ context=LineageContext(
404
+ op_kind="merge",
405
+ actor="consolidator",
406
+ source_ids=[str(s) for s in source_ids],
407
+ reason="dedup-merge",
408
+ request_id=self.service_config.request_id, # type: ignore[reportOptionalMemberAccess]
409
+ ),
410
+ )
411
+
412
+ # Leftover archives not covered by any merge group (e.g. differentiate
413
+ # split sources) have no single survivor, so preserve them as dead-source
414
+ # tombstones instead of hard-deleting them.
415
+ leftover_ids = [
416
+ pid for pid in existing_ids_to_delete if pid not in merged_source_ids
417
+ ]
418
+ if leftover_ids:
419
+ try:
420
+ superseded_count = self.storage.supersede_user_playbooks_by_ids( # type: ignore[reportOptionalMemberAccess]
421
+ leftover_ids,
422
+ self.service_config.request_id, # type: ignore[reportOptionalMemberAccess]
423
+ )
424
+ logger.info(
425
+ "Superseded %d split-source existing entries", superseded_count
426
+ )
427
+ except Exception:
428
+ logger.exception("Failed to supersede split-source existing entries")
429
+ raise
430
+
340
431
  def _enqueue_user_playbook_optimization(
341
432
  self, saved_playbooks: list[UserPlaybook]
342
433
  ) -> None:
@@ -446,10 +537,21 @@ class PlaybookGenerationService(
446
537
  )
447
538
 
448
539
  # Initialize and run aggregator (synchronous)
540
+ from reflexio.server.services.playbook.user_detail_stripping import (
541
+ create_aggregation_user_detail_stripper,
542
+ )
543
+
544
+ user_detail_stripper = create_aggregation_user_detail_stripper(
545
+ self.request_context.configurator
546
+ )
547
+ aggregator_kwargs = {}
548
+ if user_detail_stripper is not None:
549
+ aggregator_kwargs["user_detail_stripper"] = user_detail_stripper
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,84 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass, field
5
+ from typing import Protocol
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class DetectedEntity:
10
+ start: int
11
+ end: int
12
+ entity_type: str
13
+ replacement: str
14
+ confidence: float
15
+ source: str
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class StrippingResult:
20
+ text: str
21
+ detections: list[DetectedEntity] = field(default_factory=list)
22
+
23
+
24
+ class UserDetailDetector(Protocol):
25
+ def detect(self, text: str) -> list[DetectedEntity]: ...
26
+
27
+
28
+ class UserDetailStripper(Protocol):
29
+ prompt_extra_instructions: str | None
30
+
31
+ def strip_user_details(
32
+ self,
33
+ text: str,
34
+ shared_mapping: dict[str, int] | None = None,
35
+ ) -> StrippingResult: ...
36
+
37
+ def sanitize_aggregation_output_text(
38
+ self,
39
+ text: str | None,
40
+ ) -> tuple[str | None, int]: ...
41
+
42
+
43
+ class PassthroughStripper:
44
+ prompt_extra_instructions: str | None = None
45
+
46
+ def strip_user_details(
47
+ self,
48
+ text: str,
49
+ shared_mapping: dict[str, int] | None = None, # noqa: ARG002
50
+ ) -> StrippingResult:
51
+ return StrippingResult(text=text, detections=[])
52
+
53
+ def sanitize_aggregation_output_text(
54
+ self,
55
+ text: str | None,
56
+ ) -> tuple[str | None, int]:
57
+ return text, 0
58
+
59
+
60
+ UserDetailStripperFactory = Callable[[object], UserDetailStripper | None]
61
+
62
+
63
+ def _default_user_detail_stripper_factory(
64
+ _configurator: object,
65
+ ) -> UserDetailStripper | None:
66
+ return None
67
+
68
+
69
+ _user_detail_stripper_factory: UserDetailStripperFactory = (
70
+ _default_user_detail_stripper_factory
71
+ )
72
+
73
+
74
+ def set_user_detail_stripper_factory(factory: UserDetailStripperFactory) -> None:
75
+ """Register the deployment-specific aggregation stripper factory."""
76
+ global _user_detail_stripper_factory # noqa: PLW0603
77
+ _user_detail_stripper_factory = factory
78
+
79
+
80
+ def create_aggregation_user_detail_stripper(
81
+ configurator: object,
82
+ ) -> UserDetailStripper | None:
83
+ """Create the deployment-specific stripper for aggregation, if any."""
84
+ return _user_detail_stripper_factory(configurator)
@@ -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):