claude-smart 0.2.41 → 0.2.43

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 (396) hide show
  1. package/.claude-plugin/marketplace.json +17 -0
  2. package/README.md +1 -1
  3. package/bin/claude-smart.js +86 -48
  4. package/package.json +10 -3
  5. package/plugin/.claude-plugin/plugin.json +9 -3
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +2 -2
  8. package/plugin/dashboard/next.config.ts +9 -1
  9. package/plugin/pyproject.toml +2 -2
  10. package/plugin/scripts/_lib.sh +91 -0
  11. package/plugin/scripts/backend-service.sh +46 -15
  12. package/plugin/scripts/cli.sh +29 -1
  13. package/plugin/scripts/codex-hook.js +72 -4
  14. package/plugin/scripts/dashboard-build.sh +1 -0
  15. package/plugin/scripts/dashboard-service.sh +1 -0
  16. package/plugin/scripts/ensure-plugin-root.sh +7 -14
  17. package/plugin/scripts/hook_entry.sh +1 -0
  18. package/plugin/scripts/smart-install.sh +18 -2
  19. package/plugin/src/claude_smart/cli.py +72 -38
  20. package/plugin/src/claude_smart/context_format.py +11 -12
  21. package/plugin/src/claude_smart/cs_cite.py +26 -12
  22. package/plugin/src/claude_smart/ids.py +13 -5
  23. package/plugin/uv.lock +1 -1
  24. package/plugin/vendor/reflexio/.env.example +53 -0
  25. package/plugin/vendor/reflexio/LICENSE +201 -0
  26. package/plugin/vendor/reflexio/README.md +338 -0
  27. package/plugin/vendor/reflexio/pyproject.toml +271 -0
  28. package/plugin/vendor/reflexio/reflexio/README.md +184 -0
  29. package/plugin/vendor/reflexio/reflexio/__init__.py +166 -0
  30. package/plugin/vendor/reflexio/reflexio/benchmarks/__init__.py +1 -0
  31. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/README.md +109 -0
  32. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/__init__.py +1 -0
  33. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/backends.py +175 -0
  34. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/bench.py +642 -0
  35. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/embed_cache.py +330 -0
  36. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/report.py +317 -0
  37. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/report.md +43 -0
  38. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/results.json +4478 -0
  39. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/scenarios.py +134 -0
  40. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/seed.py +255 -0
  41. package/plugin/vendor/reflexio/reflexio/cli/README.md +287 -0
  42. package/plugin/vendor/reflexio/reflexio/cli/__init__.py +0 -0
  43. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +56 -0
  44. package/plugin/vendor/reflexio/reflexio/cli/_client.py +86 -0
  45. package/plugin/vendor/reflexio/reflexio/cli/app.py +127 -0
  46. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +266 -0
  47. package/plugin/vendor/reflexio/reflexio/cli/codex_auth.py +503 -0
  48. package/plugin/vendor/reflexio/reflexio/cli/commands/__init__.py +0 -0
  49. package/plugin/vendor/reflexio/reflexio/cli/commands/admin_cmd.py +65 -0
  50. package/plugin/vendor/reflexio/reflexio/cli/commands/agent_playbooks.py +503 -0
  51. package/plugin/vendor/reflexio/reflexio/cli/commands/api.py +114 -0
  52. package/plugin/vendor/reflexio/reflexio/cli/commands/auth.py +109 -0
  53. package/plugin/vendor/reflexio/reflexio/cli/commands/config_cmd.py +511 -0
  54. package/plugin/vendor/reflexio/reflexio/cli/commands/doctor.py +127 -0
  55. package/plugin/vendor/reflexio/reflexio/cli/commands/embeddings.py +53 -0
  56. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +478 -0
  57. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +303 -0
  58. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +289 -0
  59. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +961 -0
  60. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +285 -0
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/status_cmd.py +143 -0
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/user_playbooks.py +373 -0
  63. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +284 -0
  64. package/plugin/vendor/reflexio/reflexio/cli/errors.py +217 -0
  65. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +247 -0
  66. package/plugin/vendor/reflexio/reflexio/cli/output.py +867 -0
  67. package/plugin/vendor/reflexio/reflexio/cli/paths.py +41 -0
  68. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +391 -0
  69. package/plugin/vendor/reflexio/reflexio/cli/state.py +204 -0
  70. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +96 -0
  71. package/plugin/vendor/reflexio/reflexio/cli/utils.py +329 -0
  72. package/plugin/vendor/reflexio/reflexio/client/__init__.py +3 -0
  73. package/plugin/vendor/reflexio/reflexio/client/cache.py +150 -0
  74. package/plugin/vendor/reflexio/reflexio/client/client.py +2613 -0
  75. package/plugin/vendor/reflexio/reflexio/defaults.py +23 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/__init__.py +0 -0
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/.clawhubignore +7 -0
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +274 -0
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +517 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +473 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +2156 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +18 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +241 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +140 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +130 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +113 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +52 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +103 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +156 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +134 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +41 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +17 -0
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +24 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +48 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +278 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +164 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +36 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +35 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +27 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +31 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +42 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +49 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +91 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +50 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tsconfig.json +16 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/types/openclaw.d.ts +230 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +13 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/README.md +120 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/TESTING.md +168 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package-lock.json +1657 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package.json +16 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/HEARTBEAT.md +6 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/README.md +84 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/SKILL.md +194 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/_meta.json +6 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/agents/reflexio-extractor.md +45 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/handler.ts +214 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/setup.ts +55 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/index.ts +327 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/consolidate.ts +233 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/dedup.ts +80 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/io.ts +155 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/openclaw-cli.ts +67 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/search.ts +33 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-playbook.ts +76 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-profile.ts +79 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/openclaw.plugin.json +46 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/package.json +18 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/README.md +36 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/full_consolidation.md +56 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/playbook_extraction.md +217 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/profile_extraction.md +132 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-consolidate/SKILL.md +33 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-embedded/SKILL.md +194 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/HOOK.md +18 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/architecture.md +49 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/comparison.md +31 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/future-work.md +47 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/porting-notes.md +52 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/install.sh +52 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/uninstall.sh +36 -0
  142. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/consolidate.test.ts +135 -0
  143. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/dedup.test.ts +104 -0
  144. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/io.test.ts +175 -0
  145. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/search.test.ts +66 -0
  146. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/smoke-test.ts +140 -0
  147. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-playbook.test.ts +93 -0
  148. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-profile.test.ts +174 -0
  149. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tsconfig.json +16 -0
  150. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/types/openclaw.d.ts +230 -0
  151. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/vitest.config.ts +7 -0
  152. package/plugin/vendor/reflexio/reflexio/lib/__init__.py +23 -0
  153. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +310 -0
  154. package/plugin/vendor/reflexio/reflexio/lib/_base.py +225 -0
  155. package/plugin/vendor/reflexio/reflexio/lib/_config.py +83 -0
  156. package/plugin/vendor/reflexio/reflexio/lib/_dashboard.py +266 -0
  157. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +176 -0
  158. package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +334 -0
  159. package/plugin/vendor/reflexio/reflexio/lib/_operations.py +153 -0
  160. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +545 -0
  161. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +52 -0
  162. package/plugin/vendor/reflexio/reflexio/lib/_search.py +167 -0
  163. package/plugin/vendor/reflexio/reflexio/lib/_storage_labels.py +103 -0
  164. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +288 -0
  165. package/plugin/vendor/reflexio/reflexio/lib/reflexio_lib.py +27 -0
  166. package/plugin/vendor/reflexio/reflexio/models/__init__.py +0 -0
  167. package/plugin/vendor/reflexio/reflexio/models/api_schema/__init__.py +0 -0
  168. package/plugin/vendor/reflexio/reflexio/models/api_schema/braintrust_schema.py +141 -0
  169. package/plugin/vendor/reflexio/reflexio/models/api_schema/common.py +41 -0
  170. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +3 -0
  171. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +1103 -0
  172. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +63 -0
  173. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +487 -0
  174. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +28 -0
  175. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +83 -0
  176. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +766 -0
  177. package/plugin/vendor/reflexio/reflexio/models/api_schema/service_schemas.py +9 -0
  178. package/plugin/vendor/reflexio/reflexio/models/api_schema/stall_state_schema.py +32 -0
  179. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/__init__.py +3 -0
  180. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +177 -0
  181. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +129 -0
  182. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/enums.py +25 -0
  183. package/plugin/vendor/reflexio/reflexio/models/api_schema/validators.py +280 -0
  184. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +908 -0
  185. package/plugin/vendor/reflexio/reflexio/models/py.typed +0 -0
  186. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +90 -0
  187. package/plugin/vendor/reflexio/reflexio/server/README.md +616 -0
  188. package/plugin/vendor/reflexio/reflexio/server/__init__.py +210 -0
  189. package/plugin/vendor/reflexio/reflexio/server/__main__.py +132 -0
  190. package/plugin/vendor/reflexio/reflexio/server/_auth.py +25 -0
  191. package/plugin/vendor/reflexio/reflexio/server/api.py +2714 -0
  192. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/account_api.py +143 -0
  193. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/health_api.py +91 -0
  194. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/pending_tool_call_api.py +572 -0
  195. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +66 -0
  196. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +540 -0
  197. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +50 -0
  198. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/stall_state_api.py +100 -0
  199. package/plugin/vendor/reflexio/reflexio/server/cache/__init__.py +15 -0
  200. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +208 -0
  201. package/plugin/vendor/reflexio/reflexio/server/correlation.py +46 -0
  202. package/plugin/vendor/reflexio/reflexio/server/llm/__init__.py +30 -0
  203. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +110 -0
  204. package/plugin/vendor/reflexio/reflexio/server/llm/image_utils.py +55 -0
  205. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1595 -0
  206. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +112 -0
  207. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +469 -0
  208. package/plugin/vendor/reflexio/reflexio/server/llm/providers/__init__.py +1 -0
  209. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1122 -0
  210. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_stream_parser.py +197 -0
  211. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +210 -0
  212. package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +213 -0
  213. package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +255 -0
  214. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/__init__.py +6 -0
  215. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +177 -0
  216. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/llm_reranker.py +148 -0
  217. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +699 -0
  218. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +179 -0
  219. package/plugin/vendor/reflexio/reflexio/server/prompt/__init__.py +0 -0
  220. package/plugin/vendor/reflexio/reflexio/server/prompt/_dispatchers.py +54 -0
  221. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/README.md +121 -0
  222. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation/v1.0.0.prompt.md +58 -0
  223. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation_with_comparison/v1.0.0.prompt.md +76 -0
  224. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/answer_synthesis/v1.5.2.prompt.md +88 -0
  225. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/compress_session_for_query/v1.3.0.prompt.md +31 -0
  226. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +20 -0
  227. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.0.0.prompt.md +53 -0
  228. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.1.0.prompt.md +57 -0
  229. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.2.0.prompt.md +68 -0
  230. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.3.0.prompt.md +70 -0
  231. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.4.0.prompt.md +77 -0
  232. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.5.0.prompt.md +82 -0
  233. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +83 -0
  234. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.1.0.prompt.md +193 -0
  235. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +206 -0
  236. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0-deprecated.prompt.md +66 -0
  237. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0.prompt.md +43 -0
  238. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.1.0.prompt.md +46 -0
  239. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0-deprecated.prompt.md +64 -0
  240. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0.prompt.md +39 -0
  241. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.1.0.prompt.md +39 -0
  242. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.2.0.prompt.md +47 -0
  243. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.0.prompt.md +58 -0
  244. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.0.2.prompt.md +254 -0
  245. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.1.0.prompt.md +274 -0
  246. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.0.prompt.md +279 -0
  247. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v1.0.0.prompt.md +73 -0
  248. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v2.0.0.prompt.md +86 -0
  249. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.0.0.prompt.md +97 -0
  250. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.1.0.prompt.md +119 -0
  251. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.2.0.prompt.md +123 -0
  252. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +127 -0
  253. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.0.0.prompt.md +14 -0
  254. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.1.0.prompt.md +24 -0
  255. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +29 -0
  256. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.0.0.prompt.md +11 -0
  257. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.1.0.prompt.md +21 -0
  258. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.2.0.prompt.md +25 -0
  259. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.0.0.prompt.md +37 -0
  260. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.1.0.prompt.md +40 -0
  261. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.2.0.prompt.md +36 -0
  262. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v1.0.0.prompt.md +45 -0
  263. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v2.0.0.prompt.md +81 -0
  264. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v3.0.0.prompt.md +80 -0
  265. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate_expert/v1.0.0.prompt.md +34 -0
  266. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_deduplication/v1.0.0.prompt.md +116 -0
  267. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate/v1.0.0.prompt.md +33 -0
  268. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate_override/v1.0.0.prompt.md +16 -0
  269. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +140 -0
  270. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +160 -0
  271. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_main/v1.0.0.prompt.md +14 -0
  272. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +19 -0
  273. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/rerank_relevance/v1.1.0.prompt.md +44 -0
  274. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +43 -0
  275. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_content_evaluation/v1.0.0.prompt.md +33 -0
  276. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/feedback_extraction_main_v1.jsonl +10 -0
  277. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/profile_update_main_v1.jsonl +10 -0
  278. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +280 -0
  279. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_schema.py +11 -0
  280. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +131 -0
  281. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +60 -0
  282. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_service.py +228 -0
  283. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +87 -0
  284. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluator.py +372 -0
  285. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/delayed_group_evaluator.py +156 -0
  286. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/group_evaluation_runner.py +340 -0
  287. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +471 -0
  288. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +1626 -0
  289. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/__init__.py +0 -0
  290. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_cron.py +196 -0
  291. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_encryption.py +101 -0
  292. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/client.py +167 -0
  293. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/service.py +281 -0
  294. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +179 -0
  295. package/plugin/vendor/reflexio/reflexio/server/services/configurator/config_storage.py +62 -0
  296. package/plugin/vendor/reflexio/reflexio/server/services/configurator/configurator.py +87 -0
  297. package/plugin/vendor/reflexio/reflexio/server/services/configurator/local_file_config_storage.py +187 -0
  298. package/plugin/vendor/reflexio/reflexio/server/services/configurator/test_config_storage.py +162 -0
  299. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +112 -0
  300. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/__init__.py +0 -0
  301. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/distribution.py +33 -0
  302. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +126 -0
  303. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +192 -0
  304. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/hero_state.py +75 -0
  305. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/rule_attribution.py +97 -0
  306. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +515 -0
  307. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/shadow_aggregation.py +90 -0
  308. package/plugin/vendor/reflexio/reflexio/server/services/extraction/__init__.py +0 -0
  309. package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +91 -0
  310. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +303 -0
  311. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +25 -0
  312. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +351 -0
  313. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +138 -0
  314. package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +217 -0
  315. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +468 -0
  316. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +171 -0
  317. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +777 -0
  318. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +1125 -0
  319. package/plugin/vendor/reflexio/reflexio/server/services/extractor_config_utils.py +91 -0
  320. package/plugin/vendor/reflexio/reflexio/server/services/extractor_interaction_utils.py +251 -0
  321. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +689 -0
  322. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +835 -0
  323. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +89 -0
  324. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_aggregator.py +1388 -0
  325. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_consolidator.py +960 -0
  326. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_extractor.py +436 -0
  327. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_generation_service.py +808 -0
  328. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_constants.py +28 -0
  329. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +362 -0
  330. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/__init__.py +24 -0
  331. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/assistant_webhook.py +246 -0
  332. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/gepa_adapter.py +291 -0
  333. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/judge.py +97 -0
  334. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +96 -0
  335. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +645 -0
  336. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/rollout.py +35 -0
  337. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scenario_resolver.py +93 -0
  338. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +174 -0
  339. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/__init__.py +26 -0
  340. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_document_expander.py +179 -0
  341. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +297 -0
  342. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_deduplicator.py +741 -0
  343. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_extractor.py +462 -0
  344. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service.py +734 -0
  345. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +290 -0
  346. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +17 -0
  347. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_extractor.py +247 -0
  348. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service.py +800 -0
  349. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +146 -0
  350. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/__init__.py +0 -0
  351. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +70 -0
  352. package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
  353. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +671 -0
  354. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/__init__.py +1 -0
  355. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +184 -0
  356. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +81 -0
  357. package/plugin/vendor/reflexio/reflexio/server/services/storage/constants.py +2 -0
  358. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +11 -0
  359. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +154 -0
  360. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +155 -0
  361. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +59 -0
  362. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +1253 -0
  363. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +1945 -0
  364. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +600 -0
  365. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +346 -0
  366. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1378 -0
  367. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +747 -0
  368. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +263 -0
  369. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +193 -0
  370. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +166 -0
  371. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +217 -0
  372. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +153 -0
  373. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +372 -0
  374. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +71 -0
  375. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +235 -0
  376. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +170 -0
  377. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +677 -0
  378. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +250 -0
  379. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +154 -0
  380. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +130 -0
  381. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +93 -0
  382. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_stall_state.py +76 -0
  383. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +568 -0
  384. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +77 -0
  385. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +116 -0
  386. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_manager.py +263 -0
  387. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +13 -0
  388. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/llm_model_setting.json +7 -0
  389. package/plugin/vendor/reflexio/reflexio/server/tracing.py +158 -0
  390. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +113 -0
  391. package/plugin/vendor/reflexio/reflexio/server/uvicorn_logging.py +76 -0
  392. package/plugin/vendor/reflexio/reflexio/test_support/__init__.py +1 -0
  393. package/plugin/vendor/reflexio/reflexio/test_support/llm_fixtures.py +62 -0
  394. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +242 -0
  395. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +129 -0
  396. package/plugin/vendor/reflexio/reflexio/test_support/skip_decorators.py +43 -0
@@ -0,0 +1,645 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import logging
5
+ from typing import Any, Literal, cast
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from reflexio.models.api_schema.domain import (
10
+ AgentPlaybook,
11
+ AgentPlaybookSourceWindow,
12
+ PlaybookOptimizationEvent,
13
+ PlaybookOptimizationJob,
14
+ PlaybookStatus,
15
+ UserPlaybook,
16
+ )
17
+ from reflexio.models.config_schema import PlaybookOptimizerConfig
18
+ from reflexio.server.api_endpoints.request_context import RequestContext
19
+ from reflexio.server.llm.litellm_client import LiteLLMClient
20
+ from reflexio.server.tracing import sentry_tags
21
+
22
+ from .assistant_webhook import AssistantCallable, LocalScriptAssistant, WebhookAssistant
23
+ from .gepa_adapter import PLAYBOOK_CONTENT_COMPONENT, ReflexioPlaybookGEPAAdapter
24
+ from .judge import PairwiseJudge
25
+ from .models import ScenarioWindow
26
+ from .rollout import MultiTurnRollout
27
+ from .scenario_resolver import ScenarioResolver
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class PlaybookOptimizationTarget(BaseModel):
33
+ """A single playbook (agent or user) the optimizer should try to improve."""
34
+
35
+ kind: Literal["agent_playbook", "user_playbook"]
36
+ target_id: int
37
+
38
+
39
+ # Outcome of one ``optimize()`` invocation. Used by the scheduler to drive its
40
+ # abort-cooldown logic — only ``aborted`` (assistant-backend faults) trips the
41
+ # cooldown; ``failed`` (config / GEPA bugs) does not.
42
+ PlaybookOptimizationRunStatus = Literal["skipped", "completed", "failed", "aborted"]
43
+
44
+
45
+ class PlaybookOptimizer:
46
+ """Orchestrates one GEPA-driven optimization run for a single playbook.
47
+
48
+ The optimizer:
49
+
50
+ 1. Picks an assistant backend from config (``_create_assistant``).
51
+ 2. Loads the incumbent playbook and the source interaction windows that
52
+ produced it.
53
+ 3. Runs ``gepa.optimize`` with a ``ReflexioPlaybookGEPAAdapter`` — the
54
+ adapter is what actually calls the assistant and the LLM judge.
55
+ 4. Persists every candidate, evaluation, and GEPA event for offline
56
+ inspection.
57
+ 5. Optionally commits a successor playbook if the winner clears the
58
+ configured score / Likert / per-window thresholds.
59
+ """
60
+
61
+ def __init__(self, request_context: RequestContext, llm_client: LiteLLMClient):
62
+ self.request_context = request_context
63
+ if request_context.storage is None:
64
+ raise ValueError("Playbook optimizer requires storage")
65
+ self.storage = request_context.storage
66
+ self.llm_client = llm_client
67
+ self.resolver = ScenarioResolver(self.storage)
68
+
69
+ def optimize(
70
+ self, target: PlaybookOptimizationTarget
71
+ ) -> PlaybookOptimizationRunStatus:
72
+ """Run a full optimization pass for ``target``.
73
+
74
+ Returns the run status so the scheduler can react (e.g. enter abort
75
+ cooldown when the assistant backend repeatedly fails). Side-effects:
76
+ creates one ``playbook_optimization_jobs`` row and possibly archives
77
+ the incumbent in favour of a successor playbook.
78
+ """
79
+ config = self._config()
80
+ if not self._enabled_for_target(config, target):
81
+ return "skipped"
82
+ # Backend selection happens before any storage work so an unconfigured
83
+ # optimizer short-circuits cheaply — useful in tests and dev setups.
84
+ assistant = self._create_assistant(config)
85
+ if assistant is None:
86
+ logger.info(
87
+ "Skipping playbook optimization: no assistant backend configured"
88
+ )
89
+ return "skipped"
90
+
91
+ incumbent = self._load_incumbent(target)
92
+ if incumbent is None:
93
+ return "skipped"
94
+ windows = self._resolve_windows(target, config)
95
+ if not windows:
96
+ return "skipped"
97
+ train_windows, validation_windows = _split_train_validation_windows(
98
+ windows, config
99
+ )
100
+ if len(validation_windows) < config.min_commit_windows:
101
+ logger.info(
102
+ "Skipping playbook optimization: validation windows below "
103
+ "min_commit_windows target_kind=%s target_id=%d "
104
+ "validation_windows=%d min_commit_windows=%d",
105
+ target.kind,
106
+ target.target_id,
107
+ len(validation_windows),
108
+ config.min_commit_windows,
109
+ )
110
+ return "skipped"
111
+ if not _can_adopt_winner(target, config):
112
+ logger.info(
113
+ "Skipping playbook optimization: no configured adoption path "
114
+ "target_kind=%s target_id=%d",
115
+ target.kind,
116
+ target.target_id,
117
+ )
118
+ return "skipped"
119
+ split_metadata = _split_metadata(windows, train_windows, validation_windows)
120
+
121
+ job = self.storage.create_playbook_optimization_job(
122
+ PlaybookOptimizationJob(
123
+ target_kind=target.kind,
124
+ target_id=target.target_id,
125
+ status="running",
126
+ metadata_json=json.dumps(split_metadata, ensure_ascii=False),
127
+ )
128
+ )
129
+ logger.info(
130
+ "event=playbook_optimization_start job_id=%d candidate_id=none "
131
+ "target_kind=%s target_id=%d "
132
+ "windows=%d backend=%s max_metric_calls=%d max_turns=%d",
133
+ job.job_id,
134
+ target.kind,
135
+ target.target_id,
136
+ len(windows),
137
+ type(assistant).__name__,
138
+ config.max_metric_calls,
139
+ config.max_turns,
140
+ )
141
+
142
+ adapter = ReflexioPlaybookGEPAAdapter(
143
+ storage=self.storage,
144
+ job_id=job.job_id,
145
+ target_kind=target.kind,
146
+ target_id=target.target_id,
147
+ incumbent=incumbent,
148
+ rollout=MultiTurnRollout(assistant),
149
+ judge=PairwiseJudge(
150
+ self.request_context,
151
+ self.llm_client,
152
+ config.reflection_model,
153
+ ),
154
+ max_turns=config.max_turns,
155
+ )
156
+ try:
157
+ result = self._run_gepa(
158
+ config,
159
+ incumbent.content,
160
+ train_windows,
161
+ validation_windows,
162
+ adapter,
163
+ )
164
+ except Exception as exc:
165
+ with sentry_tags(
166
+ subsystem="playbook_optimizer",
167
+ op="run",
168
+ org_id=self.request_context.org_id,
169
+ job_id=job.job_id,
170
+ target_kind=target.kind,
171
+ target_id=target.target_id,
172
+ error_type=type(exc).__name__,
173
+ ):
174
+ logger.exception("Playbook optimization failed")
175
+ self.storage.update_playbook_optimization_job(
176
+ job.job_id, status="failed", decision_reason=str(exc)
177
+ )
178
+ return "failed"
179
+
180
+ best = result.best_candidate
181
+ best_content = (
182
+ best.get(PLAYBOOK_CONTENT_COMPONENT, incumbent.content)
183
+ if isinstance(best, dict)
184
+ else str(best)
185
+ )
186
+ best_score = float(result.val_aggregate_scores[result.best_idx])
187
+ winner_candidate = adapter._ensure_candidate(best_content)
188
+ self.storage.update_playbook_optimization_candidate(
189
+ winner_candidate.candidate_id,
190
+ aggregate_score=best_score,
191
+ is_winner=True,
192
+ )
193
+ logger.info(
194
+ "event=gepa_run_end job_id=%d candidate_id=%d "
195
+ "best_idx=%d best_score=%.3f num_candidates=%d",
196
+ job.job_id,
197
+ winner_candidate.candidate_id,
198
+ result.best_idx,
199
+ best_score,
200
+ len(result.val_aggregate_scores),
201
+ )
202
+
203
+ if self._has_aborted_evaluations(job.job_id):
204
+ logger.warning(
205
+ "event=playbook_optimization_aborted job_id=%d candidate_id=%d "
206
+ "reason='assistant backend aborted one or more evaluations' best_score=%.3f",
207
+ job.job_id,
208
+ winner_candidate.candidate_id,
209
+ best_score,
210
+ )
211
+ self.storage.update_playbook_optimization_job(
212
+ job.job_id,
213
+ status="failed",
214
+ best_candidate_id=winner_candidate.candidate_id,
215
+ decision_reason="assistant backend aborted one or more evaluations",
216
+ metadata_json=json.dumps(
217
+ _result_metadata(result, split_metadata),
218
+ ensure_ascii=False,
219
+ default=str,
220
+ ),
221
+ )
222
+ return "aborted"
223
+
224
+ if not self._passes_commit_thresholds(
225
+ job.job_id,
226
+ winner_candidate.candidate_id,
227
+ validation_windows,
228
+ best_score,
229
+ config,
230
+ ):
231
+ logger.info(
232
+ "event=playbook_optimization_no_commit job_id=%d candidate_id=%d "
233
+ "best_score=%.3f reason='did not pass commit thresholds'",
234
+ job.job_id,
235
+ winner_candidate.candidate_id,
236
+ best_score,
237
+ )
238
+ self.storage.update_playbook_optimization_job(
239
+ job.job_id,
240
+ status="completed",
241
+ best_candidate_id=winner_candidate.candidate_id,
242
+ decision_reason="best candidate did not pass commit thresholds",
243
+ metadata_json=json.dumps(
244
+ _result_metadata(result, split_metadata),
245
+ ensure_ascii=False,
246
+ default=str,
247
+ ),
248
+ )
249
+ return "completed"
250
+
251
+ successor_id = self._commit_if_allowed(target, incumbent, best_content, config)
252
+ logger.info(
253
+ "event=playbook_optimization_committed job_id=%d candidate_id=%d "
254
+ "successor_target_id=%s best_score=%.3f",
255
+ job.job_id,
256
+ winner_candidate.candidate_id,
257
+ successor_id if successor_id is not None else "none",
258
+ best_score,
259
+ )
260
+ self.storage.update_playbook_optimization_job(
261
+ job.job_id,
262
+ status="completed",
263
+ best_candidate_id=winner_candidate.candidate_id,
264
+ successor_target_id=successor_id,
265
+ decision_reason="committed" if successor_id else "winner persisted only",
266
+ metadata_json=json.dumps(
267
+ _result_metadata(result, split_metadata),
268
+ ensure_ascii=False,
269
+ default=str,
270
+ ),
271
+ )
272
+ return "completed"
273
+
274
+ def _run_gepa(
275
+ self,
276
+ config: PlaybookOptimizerConfig,
277
+ seed_content: str,
278
+ train_windows: list[ScenarioWindow],
279
+ validation_windows: list[ScenarioWindow],
280
+ adapter: ReflexioPlaybookGEPAAdapter,
281
+ ) -> Any:
282
+ from gepa.api import optimize as gepa_optimize
283
+ from gepa.utils.stop_condition import ScoreThresholdStopper
284
+
285
+ reflection_lm = config.reflection_model or self.llm_client.config.model
286
+ return gepa_optimize(
287
+ seed_candidate={PLAYBOOK_CONTENT_COMPONENT: seed_content},
288
+ trainset=train_windows,
289
+ valset=None
290
+ if _same_window_sequence(train_windows, validation_windows)
291
+ else validation_windows,
292
+ adapter=adapter,
293
+ reflection_lm=reflection_lm,
294
+ candidate_selection_strategy="pareto",
295
+ frontier_type="instance",
296
+ batch_sampler="epoch_shuffled",
297
+ reflection_minibatch_size=config.reflection_minibatch_size,
298
+ use_merge=config.use_merge,
299
+ max_merge_invocations=config.max_merge_invocations,
300
+ max_metric_calls=config.max_metric_calls,
301
+ stop_callbacks=[ScoreThresholdStopper(config.early_stop_score)],
302
+ raise_on_exception=False,
303
+ display_progress_bar=False,
304
+ cache_evaluation=True,
305
+ callbacks=cast(Any, [_GEPAStorageCallback(self.storage, adapter.job_id)]),
306
+ )
307
+
308
+ def _config(self) -> PlaybookOptimizerConfig:
309
+ config = self.request_context.configurator.get_config()
310
+ return config.playbook_optimizer_config
311
+
312
+ def _create_assistant(
313
+ self, config: PlaybookOptimizerConfig
314
+ ) -> AssistantCallable | None:
315
+ """Pick the assistant backend implied by config.
316
+
317
+ ``webhook_url`` and ``assistant_script_path`` are mutually exclusive
318
+ (validated in ``PlaybookOptimizerConfig``), so this is a simple two-way
319
+ dispatch. Returning ``None`` means the optimizer is enabled but has no
320
+ backend configured — ``optimize()`` treats that as a no-op.
321
+
322
+ The ``webhook_*`` retry/timeout fields govern *both* backends; the
323
+ prefix is preserved only for config-schema compatibility.
324
+ """
325
+ if config.webhook_url:
326
+ return WebhookAssistant(
327
+ url=config.webhook_url,
328
+ auth_header=config.webhook_auth_header,
329
+ timeout_s=config.webhook_timeout_seconds,
330
+ max_retries=config.webhook_max_retries,
331
+ backoff_base_s=config.webhook_backoff_base_seconds,
332
+ )
333
+ if config.assistant_script_path:
334
+ return LocalScriptAssistant(
335
+ script_path=config.assistant_script_path,
336
+ script_args=config.assistant_script_args,
337
+ timeout_s=config.webhook_timeout_seconds,
338
+ max_retries=config.webhook_max_retries,
339
+ backoff_base_s=config.webhook_backoff_base_seconds,
340
+ )
341
+ return None
342
+
343
+ def _enabled_for_target(
344
+ self, config: PlaybookOptimizerConfig, target: PlaybookOptimizationTarget
345
+ ) -> bool:
346
+ if not config.enabled:
347
+ return False
348
+ if target.kind == "agent_playbook":
349
+ return config.optimize_agent_playbooks
350
+ return config.optimize_user_playbooks
351
+
352
+ def _load_incumbent(
353
+ self, target: PlaybookOptimizationTarget
354
+ ) -> AgentPlaybook | None:
355
+ if target.kind == "agent_playbook":
356
+ playbook = self.storage.get_agent_playbook_by_id(target.target_id)
357
+ if (
358
+ playbook is None
359
+ or playbook.status is not None
360
+ or playbook.playbook_status != PlaybookStatus.PENDING
361
+ ):
362
+ return None
363
+ return playbook
364
+ user_playbook = self.storage.get_user_playbook_by_id(target.target_id)
365
+ if user_playbook is None or user_playbook.status is not None:
366
+ return None
367
+ return _agent_like_playbook(user_playbook)
368
+
369
+ def _resolve_windows(
370
+ self, target: PlaybookOptimizationTarget, config: PlaybookOptimizerConfig
371
+ ) -> list:
372
+ if target.kind == "agent_playbook":
373
+ return self.resolver.for_agent_playbook(target.target_id)
374
+ if not config.optimize_user_playbooks:
375
+ return []
376
+ return self.resolver.for_user_playbook(target.target_id)
377
+
378
+ def _passes_commit_thresholds(
379
+ self,
380
+ job_id: int,
381
+ candidate_id: int,
382
+ validation_windows: list[ScenarioWindow],
383
+ best_score: float,
384
+ config: PlaybookOptimizerConfig,
385
+ ) -> bool:
386
+ if best_score < config.min_commit_score:
387
+ return False
388
+ evaluations = self.storage.list_playbook_optimization_evaluations(job_id)
389
+ validation_keys = {_window_eval_key(window) for window in validation_windows}
390
+ winning_windows = {
391
+ _evaluation_key(
392
+ evaluation.scenario_user_playbook_id,
393
+ evaluation.source_interaction_ids,
394
+ )
395
+ for evaluation in evaluations
396
+ if evaluation.candidate_id == candidate_id
397
+ and _evaluation_key(
398
+ evaluation.scenario_user_playbook_id,
399
+ evaluation.source_interaction_ids,
400
+ )
401
+ in validation_keys
402
+ and evaluation.verdict == "candidate"
403
+ and evaluation.score >= config.min_commit_score
404
+ and evaluation.likert >= config.min_commit_likert
405
+ }
406
+ return len(winning_windows) >= config.min_commit_windows
407
+
408
+ def _has_aborted_evaluations(self, job_id: int) -> bool:
409
+ evaluations = self.storage.list_playbook_optimization_evaluations(job_id)
410
+ return any(evaluation.verdict == "aborted" for evaluation in evaluations)
411
+
412
+ def _commit_if_allowed(
413
+ self,
414
+ target: PlaybookOptimizationTarget,
415
+ incumbent: AgentPlaybook,
416
+ best_content: str,
417
+ config: PlaybookOptimizerConfig,
418
+ ) -> int | None:
419
+ # The optimize() entrypoint already gates on _can_adopt_winner before
420
+ # creating the job, but check again so this stays correct if called
421
+ # from elsewhere.
422
+ if not _can_adopt_winner(target, config):
423
+ return None
424
+ if target.kind == "agent_playbook":
425
+ source_windows = _source_windows_with_backfill(
426
+ self.storage, target.target_id
427
+ )
428
+ current = self.storage.get_agent_playbook_by_id(target.target_id)
429
+ if (
430
+ current is None
431
+ or current.status is not None
432
+ or current.playbook_status != PlaybookStatus.PENDING
433
+ ):
434
+ 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
+ }
446
+ )
447
+ saved = self.storage.save_agent_playbooks([successor])
448
+ if saved and saved[0].agent_playbook_id:
449
+ self.storage.set_source_windows_for_agent_playbook(
450
+ saved[0].agent_playbook_id, source_windows
451
+ )
452
+ return saved[0].agent_playbook_id
453
+ return None
454
+ current_user = self.storage.get_user_playbook_by_id(target.target_id)
455
+ if current_user is None or current_user.status is not None:
456
+ return None
457
+ if current_user.user_id is None:
458
+ 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
+ # The optimizer can legitimately flip framing (positive guidance ->
465
+ # negative anti-pattern or vice versa). Orientation lives entirely in
466
+ # the rule wording, so writing ``best_content`` is sufficient — there
467
+ # is no derived polarity label or separate polarity field to keep in
468
+ # sync.
469
+ successor_user = current_user.model_copy(
470
+ update={
471
+ "user_playbook_id": 0,
472
+ "content": best_content,
473
+ "status": None,
474
+ }
475
+ )
476
+ self.storage.save_user_playbooks([successor_user])
477
+ return successor_user.user_playbook_id or None
478
+
479
+
480
+ def _agent_like_playbook(playbook: UserPlaybook) -> AgentPlaybook:
481
+ return AgentPlaybook(
482
+ agent_playbook_id=playbook.user_playbook_id,
483
+ playbook_name=playbook.playbook_name,
484
+ agent_version=playbook.agent_version,
485
+ content=playbook.content,
486
+ trigger=playbook.trigger,
487
+ rationale=playbook.rationale,
488
+ playbook_status=PlaybookStatus.PENDING,
489
+ status=playbook.status,
490
+ )
491
+
492
+
493
+ def _can_adopt_winner(
494
+ target: PlaybookOptimizationTarget,
495
+ config: PlaybookOptimizerConfig,
496
+ ) -> bool:
497
+ if target.kind == "agent_playbook":
498
+ return config.auto_update_pending_agent_playbooks
499
+ return config.auto_update_user_playbooks
500
+
501
+
502
+ def _append_optimizer_metadata(existing: str, predecessor_id: int) -> str:
503
+ suffix = f"optimized_from_agent_playbook_id={predecessor_id}"
504
+ if not existing:
505
+ return suffix
506
+ return f"{existing}; {suffix}"
507
+
508
+
509
+ class _GEPAStorageCallback:
510
+ def __init__(self, storage: Any, job_id: int) -> None:
511
+ self.storage = storage
512
+ self.job_id = job_id
513
+
514
+ def __getattr__(self, name: str) -> Any:
515
+ if not name.startswith("on_"):
516
+ raise AttributeError(name)
517
+
518
+ def _record(event: dict[str, Any]) -> None:
519
+ self.storage.insert_playbook_optimization_event(
520
+ PlaybookOptimizationEvent(
521
+ job_id=self.job_id,
522
+ event_type=name.removeprefix("on_"),
523
+ payload_json=json.dumps(
524
+ _safe_event_payload(event), ensure_ascii=False, default=str
525
+ ),
526
+ )
527
+ )
528
+
529
+ return _record
530
+
531
+
532
+ def _safe_event_payload(value: Any, depth: int = 0) -> Any:
533
+ if depth > 3:
534
+ return str(value)
535
+ if isinstance(value, dict):
536
+ return {
537
+ str(k): _safe_event_payload(v, depth + 1)
538
+ for k, v in value.items()
539
+ if k != "final_state"
540
+ }
541
+ if isinstance(value, list | tuple | set):
542
+ return [_safe_event_payload(v, depth + 1) for v in value]
543
+ if isinstance(value, str | int | float | bool) or value is None:
544
+ return value
545
+ if hasattr(value, "model_dump"):
546
+ return _safe_event_payload(value.model_dump(), depth + 1)
547
+ return str(value)
548
+
549
+
550
+ def _split_train_validation_windows(
551
+ windows: list[ScenarioWindow],
552
+ config: PlaybookOptimizerConfig,
553
+ ) -> tuple[list[ScenarioWindow], list[ScenarioWindow]]:
554
+ if len(windows) <= 1:
555
+ return windows, windows
556
+
557
+ ordered_windows = sorted(
558
+ windows,
559
+ key=lambda window: (
560
+ window.user_playbook_id if window.user_playbook_id is not None else -1,
561
+ tuple(window.source_interaction_ids),
562
+ ),
563
+ )
564
+ validation_count = min(config.max_validation_windows, len(ordered_windows) - 1)
565
+ validation_windows = ordered_windows[:validation_count]
566
+ train_windows = ordered_windows[validation_count:]
567
+ return train_windows, validation_windows
568
+
569
+
570
+ def _same_window_sequence(
571
+ left: list[ScenarioWindow],
572
+ right: list[ScenarioWindow],
573
+ ) -> bool:
574
+ if len(left) != len(right):
575
+ return False
576
+ return all(
577
+ _window_eval_key(left_window) == _window_eval_key(right_window)
578
+ for left_window, right_window in zip(left, right, strict=True)
579
+ )
580
+
581
+
582
+ def _source_windows_with_backfill(
583
+ storage: Any, agent_playbook_id: int
584
+ ) -> list[AgentPlaybookSourceWindow]:
585
+ source_windows = storage.get_source_windows_for_agent_playbook(agent_playbook_id)
586
+ missing_ids = [
587
+ window.user_playbook_id
588
+ for window in source_windows
589
+ if not window.source_interaction_ids
590
+ ]
591
+ if not missing_ids:
592
+ return source_windows
593
+
594
+ playbooks = storage.get_user_playbooks_by_ids_any_user(
595
+ missing_ids, status_filter=None
596
+ )
597
+ source_ids_by_playbook_id = {
598
+ playbook.user_playbook_id: playbook.source_interaction_ids
599
+ for playbook in playbooks
600
+ if playbook.source_interaction_ids
601
+ }
602
+ return [
603
+ window
604
+ if window.source_interaction_ids
605
+ else AgentPlaybookSourceWindow(
606
+ user_playbook_id=window.user_playbook_id,
607
+ source_interaction_ids=list(
608
+ source_ids_by_playbook_id.get(window.user_playbook_id, [])
609
+ ),
610
+ )
611
+ for window in source_windows
612
+ ]
613
+
614
+
615
+ def _split_metadata(
616
+ windows: list[ScenarioWindow],
617
+ train_windows: list[ScenarioWindow],
618
+ validation_windows: list[ScenarioWindow],
619
+ ) -> dict[str, Any]:
620
+ return {
621
+ "source_window_count": len(windows),
622
+ "train_window_count": len(train_windows),
623
+ "validation_window_count": len(validation_windows),
624
+ "validation_scenario_user_playbook_ids": [
625
+ window.user_playbook_id for window in validation_windows
626
+ ],
627
+ }
628
+
629
+
630
+ def _result_metadata(result: Any, split_metadata: dict[str, Any]) -> dict[str, Any]:
631
+ metadata = result.to_dict()
632
+ if not isinstance(metadata, dict):
633
+ metadata = {"gepa_result": metadata}
634
+ metadata.update(split_metadata)
635
+ return metadata
636
+
637
+
638
+ def _window_eval_key(window: ScenarioWindow) -> tuple[int | None, tuple[int, ...]]:
639
+ return window.user_playbook_id, tuple(window.source_interaction_ids)
640
+
641
+
642
+ def _evaluation_key(
643
+ scenario_user_playbook_id: int | None, source_interaction_ids: list[int]
644
+ ) -> tuple[int | None, tuple[int, ...]]:
645
+ return scenario_user_playbook_id, tuple(source_interaction_ids)
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from reflexio.models.api_schema.domain import AgentPlaybook
4
+
5
+ from .assistant_webhook import AssistantCallable
6
+ from .models import ChatMessage, RolloutTrace, ScenarioWindow
7
+
8
+
9
+ class MultiTurnRollout:
10
+ """Replay a scenario's user turns against an assistant backend.
11
+
12
+ For each user turn in ``window.user_turns`` (capped by ``max_turns``),
13
+ the rollout appends the user message, asks the backend for a reply, and
14
+ appends the reply to the history. Importantly, the *user side* is fixed
15
+ — replayed verbatim from the recorded scenario — so a paired
16
+ incumbent/candidate run differs only in the playbook injected into the
17
+ backend. That isolation is what makes the judge's verdict meaningful.
18
+ """
19
+
20
+ def __init__(self, assistant: AssistantCallable) -> None:
21
+ self.assistant = assistant
22
+
23
+ def run(
24
+ self,
25
+ *,
26
+ window: ScenarioWindow,
27
+ playbook: AgentPlaybook,
28
+ max_turns: int,
29
+ ) -> RolloutTrace:
30
+ history: list[ChatMessage] = []
31
+ for user_turn in window.user_turns[:max_turns]:
32
+ history.append(user_turn)
33
+ assistant_content = self.assistant(history, [playbook])
34
+ history.append(ChatMessage(role="assistant", content=assistant_content))
35
+ return RolloutTrace(messages=history, playbook_content=playbook.content)