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,1378 @@
1
+ """Playbook CRUD + search methods for SQLite storage."""
2
+
3
+ import json
4
+ import sqlite3
5
+ from concurrent.futures import ThreadPoolExecutor
6
+ from typing import Any
7
+
8
+ from reflexio.models.api_schema.common import BlockingIssue
9
+ from reflexio.models.api_schema.retriever_schema import (
10
+ SearchAgentPlaybookRequest,
11
+ SearchUserPlaybookRequest,
12
+ )
13
+ from reflexio.models.api_schema.service_schemas import (
14
+ AgentPlaybook,
15
+ AgentPlaybookSourceWindow,
16
+ AgentSuccessEvaluationResult,
17
+ PlaybookOptimizationCandidate,
18
+ PlaybookOptimizationEvaluation,
19
+ PlaybookOptimizationEvent,
20
+ PlaybookOptimizationJob,
21
+ PlaybookStatus,
22
+ Status,
23
+ UserPlaybook,
24
+ )
25
+ from reflexio.models.config_schema import SearchMode, SearchOptions
26
+
27
+ from ._base import (
28
+ SQLiteStorageBase,
29
+ _build_status_sql,
30
+ _effective_search_mode,
31
+ _epoch_to_iso,
32
+ _json_dumps,
33
+ _json_loads,
34
+ _row_to_agent_playbook,
35
+ _row_to_eval_result,
36
+ _row_to_user_playbook,
37
+ _sanitize_fts_query,
38
+ _true_rrf_merge,
39
+ _vector_rank_rows,
40
+ )
41
+
42
+
43
+ def _row_to_playbook_optimization_candidate(
44
+ row: sqlite3.Row,
45
+ ) -> PlaybookOptimizationCandidate:
46
+ return PlaybookOptimizationCandidate(
47
+ candidate_id=row["candidate_id"],
48
+ job_id=row["job_id"],
49
+ candidate_index=row["candidate_index"],
50
+ content=row["content"],
51
+ parent_candidate_ids=_json_loads(row["parent_candidate_ids"]) or [],
52
+ aggregate_score=row["aggregate_score"],
53
+ is_winner=bool(row["is_winner"]),
54
+ created_at=row["created_at"],
55
+ )
56
+
57
+
58
+ def _row_to_playbook_optimization_evaluation(
59
+ row: sqlite3.Row,
60
+ ) -> PlaybookOptimizationEvaluation:
61
+ return PlaybookOptimizationEvaluation(
62
+ evaluation_id=row["evaluation_id"],
63
+ job_id=row["job_id"],
64
+ candidate_id=row["candidate_id"],
65
+ target_kind=row["target_kind"],
66
+ target_id=row["target_id"],
67
+ scenario_user_playbook_id=row["scenario_user_playbook_id"],
68
+ source_interaction_ids=_json_loads(row["source_interaction_ids"]) or [],
69
+ score=row["score"],
70
+ verdict=row["verdict"],
71
+ likert=row["likert"],
72
+ rationale=row["rationale"],
73
+ asi_json=row["asi_json"],
74
+ incumbent_rollout_json=row["incumbent_rollout_json"],
75
+ candidate_rollout_json=row["candidate_rollout_json"],
76
+ created_at=row["created_at"],
77
+ )
78
+
79
+
80
+ class PlaybookMixin:
81
+ """Mixin providing user playbook, agent playbook, and evaluation CRUD + search."""
82
+
83
+ # Type hints for instance attributes/methods provided by SQLiteStorageBase via MRO
84
+ _lock: Any
85
+ conn: sqlite3.Connection
86
+ _execute: Any
87
+ _fetchone: Any
88
+ _fetchall: Any
89
+ _get_embedding: Any
90
+ _should_expand_documents: Any
91
+ _expand_document: Any
92
+ _fts_upsert: Any
93
+ _fts_delete: Any
94
+ _vec_upsert: Any
95
+ _vec_delete: Any
96
+ _has_sqlite_vec: bool
97
+
98
+ # ------------------------------------------------------------------
99
+ # User Playbook methods
100
+ # ------------------------------------------------------------------
101
+
102
+ @SQLiteStorageBase.handle_exceptions
103
+ def save_user_playbooks(self, user_playbooks: list[UserPlaybook]) -> None:
104
+ for up in user_playbooks:
105
+ embedding_text = up.trigger or up.content
106
+ if embedding_text:
107
+ if self._should_expand_documents():
108
+ with ThreadPoolExecutor(max_workers=2) as executor:
109
+ emb_future = executor.submit(
110
+ self._get_embedding, embedding_text
111
+ )
112
+ exp_future = executor.submit(
113
+ self._expand_document, embedding_text
114
+ )
115
+ up.embedding = emb_future.result(timeout=15)
116
+ up.expanded_terms = exp_future.result(timeout=15)
117
+ else:
118
+ up.embedding = self._get_embedding(embedding_text)
119
+
120
+ created_at_iso = _epoch_to_iso(up.created_at)
121
+ with self._lock:
122
+ cur = self.conn.execute(
123
+ """INSERT INTO user_playbooks
124
+ (user_id, playbook_name, created_at, request_id, agent_version,
125
+ content, trigger, rationale, blocking_issue,
126
+ source_interaction_ids,
127
+ status, source, embedding, expanded_terms,
128
+ source_span, notes, reader_angle)
129
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
130
+ (
131
+ up.user_id,
132
+ up.playbook_name,
133
+ created_at_iso,
134
+ up.request_id,
135
+ up.agent_version,
136
+ up.content,
137
+ up.trigger,
138
+ up.rationale,
139
+ json.dumps(up.blocking_issue.model_dump())
140
+ if up.blocking_issue
141
+ else None,
142
+ _json_dumps(up.source_interaction_ids or None),
143
+ up.status.value if up.status else None,
144
+ up.source,
145
+ _json_dumps(up.embedding),
146
+ up.expanded_terms,
147
+ up.source_span,
148
+ up.notes,
149
+ up.reader_angle,
150
+ ),
151
+ )
152
+ upid = cur.lastrowid or 0
153
+ up.user_playbook_id = upid
154
+ self.conn.commit()
155
+
156
+ fts_parts = [up.trigger or "", up.content or ""]
157
+ if up.expanded_terms:
158
+ fts_parts.append(up.expanded_terms)
159
+ self._fts_upsert(
160
+ "user_playbooks_fts",
161
+ upid,
162
+ search_text=" ".join(p for p in fts_parts if p) or "",
163
+ )
164
+ if up.embedding:
165
+ self._vec_upsert("user_playbooks_vec", upid, up.embedding)
166
+
167
+ @SQLiteStorageBase.handle_exceptions
168
+ def get_user_playbooks(
169
+ self,
170
+ limit: int = 100,
171
+ user_id: str | None = None,
172
+ playbook_name: str | None = None,
173
+ agent_version: str | None = None,
174
+ status_filter: list[Status | None] | None = None,
175
+ start_time: int | None = None,
176
+ end_time: int | None = None,
177
+ include_embedding: bool = False,
178
+ ) -> list[UserPlaybook]:
179
+ sql = "SELECT * FROM user_playbooks WHERE 1=1"
180
+ params: list[Any] = []
181
+
182
+ if user_id is not None:
183
+ sql += " AND user_id = ?"
184
+ params.append(user_id)
185
+ if playbook_name:
186
+ sql += " AND playbook_name = ?"
187
+ params.append(playbook_name)
188
+ if agent_version is not None:
189
+ sql += " AND agent_version = ?"
190
+ params.append(agent_version)
191
+ if start_time is not None:
192
+ sql += " AND created_at >= ?"
193
+ params.append(_epoch_to_iso(start_time))
194
+ if end_time is not None:
195
+ sql += " AND created_at <= ?"
196
+ params.append(_epoch_to_iso(end_time))
197
+ if status_filter is not None:
198
+ frag, sparams = _build_status_sql(status_filter)
199
+ sql += f" AND {frag}"
200
+ params.extend(sparams)
201
+
202
+ sql += " ORDER BY created_at DESC LIMIT ?"
203
+ params.append(limit)
204
+ rows = self._fetchall(sql, params)
205
+ return [
206
+ _row_to_user_playbook(r, include_embedding=include_embedding) for r in rows
207
+ ]
208
+
209
+ @SQLiteStorageBase.handle_exceptions
210
+ def count_user_playbooks(
211
+ self,
212
+ user_id: str | None = None,
213
+ playbook_name: str | None = None,
214
+ min_user_playbook_id: int | None = None,
215
+ agent_version: str | None = None,
216
+ status_filter: list[Status | None] | None = None,
217
+ ) -> int:
218
+ sql = "SELECT COUNT(*) as cnt FROM user_playbooks WHERE 1=1"
219
+ params: list[Any] = []
220
+
221
+ if user_id is not None:
222
+ sql += " AND user_id = ?"
223
+ params.append(user_id)
224
+ if playbook_name:
225
+ sql += " AND playbook_name = ?"
226
+ params.append(playbook_name)
227
+ if min_user_playbook_id is not None:
228
+ sql += " AND user_playbook_id > ?"
229
+ params.append(min_user_playbook_id)
230
+ if agent_version is not None:
231
+ sql += " AND agent_version = ?"
232
+ params.append(agent_version)
233
+ if status_filter is not None:
234
+ frag, sparams = _build_status_sql(status_filter)
235
+ sql += f" AND {frag}"
236
+ params.extend(sparams)
237
+
238
+ row = self._fetchone(sql, params)
239
+ return row["cnt"] if row else 0
240
+
241
+ @SQLiteStorageBase.handle_exceptions
242
+ def count_user_playbooks_by_session(self, session_id: str) -> int:
243
+ row = self._fetchone(
244
+ """SELECT COUNT(*) as cnt FROM user_playbooks up
245
+ JOIN requests r ON up.request_id = r.request_id
246
+ WHERE r.session_id = ?""",
247
+ (session_id,),
248
+ )
249
+ return row["cnt"] if row else 0
250
+
251
+ @SQLiteStorageBase.handle_exceptions
252
+ def delete_all_user_playbooks(self) -> None:
253
+ with self._lock:
254
+ self.conn.execute("DELETE FROM user_playbooks_fts")
255
+ self.conn.execute("DELETE FROM user_playbooks")
256
+ self.conn.commit()
257
+
258
+ @SQLiteStorageBase.handle_exceptions
259
+ def delete_user_playbook(self, user_playbook_id: int) -> None:
260
+ self._fts_delete("user_playbooks_fts", user_playbook_id)
261
+ self._vec_delete("user_playbooks_vec", user_playbook_id)
262
+ self._execute(
263
+ "DELETE FROM user_playbooks WHERE user_playbook_id = ?", (user_playbook_id,)
264
+ )
265
+
266
+ @SQLiteStorageBase.handle_exceptions
267
+ def delete_all_user_playbooks_by_playbook_name(
268
+ self, playbook_name: str, agent_version: str | None = None
269
+ ) -> None:
270
+ sql = "SELECT user_playbook_id FROM user_playbooks WHERE playbook_name = ?"
271
+ params: list[Any] = [playbook_name]
272
+ if agent_version is not None:
273
+ sql += " AND agent_version = ?"
274
+ params.append(agent_version)
275
+ ids = [r["user_playbook_id"] for r in self._fetchall(sql, params)]
276
+ if ids:
277
+ ph = ",".join("?" for _ in ids)
278
+ with self._lock:
279
+ self.conn.execute(
280
+ f"DELETE FROM user_playbooks_fts WHERE rowid IN ({ph})", ids
281
+ )
282
+ self.conn.commit()
283
+
284
+ del_sql = "DELETE FROM user_playbooks WHERE playbook_name = ?"
285
+ del_params: list[Any] = [playbook_name]
286
+ if agent_version is not None:
287
+ del_sql += " AND agent_version = ?"
288
+ del_params.append(agent_version)
289
+ self._execute(del_sql, del_params)
290
+
291
+ @SQLiteStorageBase.handle_exceptions
292
+ def delete_user_playbooks_by_ids(self, user_playbook_ids: list[int]) -> int:
293
+ if not user_playbook_ids:
294
+ return 0
295
+ ph = ",".join("?" for _ in user_playbook_ids)
296
+ with self._lock:
297
+ self.conn.execute(
298
+ f"DELETE FROM user_playbooks_fts WHERE rowid IN ({ph})",
299
+ user_playbook_ids,
300
+ )
301
+ cur = self.conn.execute(
302
+ f"DELETE FROM user_playbooks WHERE user_playbook_id IN ({ph})",
303
+ user_playbook_ids,
304
+ )
305
+ self.conn.commit()
306
+ return cur.rowcount
307
+
308
+ @SQLiteStorageBase.handle_exceptions
309
+ def update_all_user_playbooks_status(
310
+ self,
311
+ old_status: Status | None,
312
+ new_status: Status | None,
313
+ agent_version: str | None = None,
314
+ playbook_name: str | None = None,
315
+ ) -> int:
316
+ new_val = new_status.value if new_status else None
317
+ params: list[Any] = [new_val]
318
+
319
+ if old_status is None or (
320
+ hasattr(old_status, "value") and old_status.value is None
321
+ ):
322
+ where = "status IS NULL"
323
+ else:
324
+ where = "status = ?"
325
+ params.append(old_status.value)
326
+
327
+ if agent_version is not None:
328
+ where += " AND agent_version = ?"
329
+ params.append(agent_version)
330
+ if playbook_name is not None:
331
+ where += " AND playbook_name = ?"
332
+ params.append(playbook_name)
333
+
334
+ cur = self._execute(
335
+ f"UPDATE user_playbooks SET status = ? WHERE {where}", params
336
+ )
337
+ return cur.rowcount
338
+
339
+ @SQLiteStorageBase.handle_exceptions
340
+ def delete_all_user_playbooks_by_status(
341
+ self,
342
+ status: Status,
343
+ agent_version: str | None = None,
344
+ playbook_name: str | None = None,
345
+ ) -> int:
346
+ where = "status = ?"
347
+ params: list[Any] = [status.value]
348
+ if agent_version is not None:
349
+ where += " AND agent_version = ?"
350
+ params.append(agent_version)
351
+ if playbook_name is not None:
352
+ where += " AND playbook_name = ?"
353
+ params.append(playbook_name)
354
+
355
+ # Clean up FTS
356
+ ids = [
357
+ r["user_playbook_id"]
358
+ for r in self._fetchall(
359
+ f"SELECT user_playbook_id FROM user_playbooks WHERE {where}", params
360
+ )
361
+ ]
362
+ if ids:
363
+ ph = ",".join("?" for _ in ids)
364
+ with self._lock:
365
+ self.conn.execute(
366
+ f"DELETE FROM user_playbooks_fts WHERE rowid IN ({ph})", ids
367
+ )
368
+ self.conn.commit()
369
+
370
+ cur = self._execute(f"DELETE FROM user_playbooks WHERE {where}", params)
371
+ return cur.rowcount
372
+
373
+ @SQLiteStorageBase.handle_exceptions
374
+ def get_user_playbooks_by_ids(
375
+ self,
376
+ user_id: str,
377
+ user_playbook_ids: list[int],
378
+ status_filter: list[Status | None] | None = None,
379
+ ) -> list[UserPlaybook]:
380
+ if not user_playbook_ids:
381
+ return []
382
+ if status_filter is None:
383
+ status_filter = [None]
384
+ frag, sparams = _build_status_sql(status_filter)
385
+ ph = ",".join("?" for _ in user_playbook_ids)
386
+ sql = (
387
+ f"SELECT * FROM user_playbooks "
388
+ f"WHERE user_id = ? AND user_playbook_id IN ({ph}) AND {frag}"
389
+ )
390
+ params: list[Any] = [user_id, *user_playbook_ids, *sparams]
391
+ return [_row_to_user_playbook(r) for r in self._fetchall(sql, params)]
392
+
393
+ @SQLiteStorageBase.handle_exceptions
394
+ def archive_user_playbook_by_id(self, user_id: str, user_playbook_id: int) -> bool:
395
+ cur = self._execute(
396
+ "UPDATE user_playbooks SET status = ? "
397
+ "WHERE user_playbook_id = ? AND user_id = ? AND status IS NULL",
398
+ (Status.ARCHIVED.value, user_playbook_id, user_id),
399
+ )
400
+ return cur.rowcount > 0
401
+
402
+ @SQLiteStorageBase.handle_exceptions
403
+ def has_user_playbooks_with_status(
404
+ self,
405
+ status: Status | None,
406
+ agent_version: str | None = None,
407
+ playbook_name: str | None = None,
408
+ ) -> bool:
409
+ sql = "SELECT 1 FROM user_playbooks WHERE "
410
+ params: list[Any] = []
411
+
412
+ if status is None or (hasattr(status, "value") and status.value is None):
413
+ sql += "status IS NULL"
414
+ else:
415
+ sql += "status = ?"
416
+ params.append(status.value)
417
+
418
+ if agent_version is not None:
419
+ sql += " AND agent_version = ?"
420
+ params.append(agent_version)
421
+ if playbook_name is not None:
422
+ sql += " AND playbook_name = ?"
423
+ params.append(playbook_name)
424
+
425
+ sql += " LIMIT 1"
426
+ row = self._fetchone(sql, params)
427
+ return row is not None
428
+
429
+ @SQLiteStorageBase.handle_exceptions
430
+ def search_user_playbooks( # noqa: C901
431
+ self,
432
+ request: SearchUserPlaybookRequest,
433
+ options: SearchOptions | None = None,
434
+ ) -> list[UserPlaybook]:
435
+ query = request.query
436
+ user_id = request.user_id
437
+ agent_version = request.agent_version
438
+ playbook_name = request.playbook_name
439
+ start_time = int(request.start_time.timestamp()) if request.start_time else None
440
+ end_time = int(request.end_time.timestamp()) if request.end_time else None
441
+ status_filter = request.status_filter
442
+ match_count = request.top_k or 10
443
+ query_embedding = options.query_embedding if options else None
444
+ mode = _effective_search_mode(
445
+ request.search_mode, query_embedding, request.query
446
+ )
447
+ rrf_k = options.rrf_k if options else 60
448
+ vector_weight = options.vector_weight if options else 1.0
449
+ fts_weight = options.fts_weight if options else 1.0
450
+
451
+ conditions: list[str] = []
452
+ params: list[Any] = []
453
+
454
+ if user_id:
455
+ conditions.append("up.user_id = ?")
456
+ params.append(user_id)
457
+ if agent_version:
458
+ conditions.append("up.agent_version = ?")
459
+ params.append(agent_version)
460
+ if playbook_name:
461
+ conditions.append("up.playbook_name = ?")
462
+ params.append(playbook_name)
463
+ if start_time:
464
+ conditions.append("up.created_at >= ?")
465
+ params.append(_epoch_to_iso(start_time))
466
+ if end_time:
467
+ conditions.append("up.created_at <= ?")
468
+ params.append(_epoch_to_iso(end_time))
469
+ if status_filter is not None:
470
+ frag, sparams = _build_status_sql(status_filter)
471
+ conditions.append(frag)
472
+ params.extend(sparams)
473
+
474
+ where_extra = (" AND " + " AND ".join(conditions)) if conditions else ""
475
+ overfetch = match_count * 5 if mode != SearchMode.FTS else match_count
476
+
477
+ # Pure vector search: fetch all candidates, rank by cosine similarity
478
+ if mode == SearchMode.VECTOR and query_embedding:
479
+ base_where = "WHERE " + " AND ".join(conditions) if conditions else ""
480
+ sql = f"""SELECT * FROM user_playbooks up
481
+ {base_where}
482
+ ORDER BY up.created_at DESC"""
483
+ rows = self._fetchall(sql, params)
484
+ rows = _vector_rank_rows(rows, query_embedding, match_count)
485
+ return [_row_to_user_playbook(r) for r in rows]
486
+
487
+ if query:
488
+ fts_query = _sanitize_fts_query(query)
489
+ sql = f"""SELECT up.* FROM user_playbooks up
490
+ JOIN user_playbooks_fts f ON up.user_playbook_id = f.rowid
491
+ WHERE user_playbooks_fts MATCH ?{where_extra}
492
+ ORDER BY bm25(user_playbooks_fts, 1.0)
493
+ LIMIT ?"""
494
+ fts_rows = self._fetchall(sql, [fts_query, *params, overfetch])
495
+
496
+ if mode == SearchMode.HYBRID and query_embedding:
497
+ base_where = "WHERE " + " AND ".join(conditions) if conditions else ""
498
+ vec_limit = match_count * 10
499
+ vec_sql = f"""SELECT * FROM user_playbooks up
500
+ {base_where}
501
+ ORDER BY up.created_at DESC
502
+ LIMIT ?"""
503
+ vec_candidates = self._fetchall(vec_sql, [*params, vec_limit])
504
+ vec_rows = _vector_rank_rows(vec_candidates, query_embedding, overfetch)
505
+ rows = _true_rrf_merge(
506
+ fts_rows,
507
+ vec_rows,
508
+ "user_playbook_id",
509
+ match_count,
510
+ rrf_k,
511
+ vector_weight,
512
+ fts_weight,
513
+ )
514
+ return [_row_to_user_playbook(r) for r in rows]
515
+ return [_row_to_user_playbook(r) for r in fts_rows[:match_count]]
516
+
517
+ # HYBRID without query text: rank by embedding only
518
+ if query_embedding:
519
+ base_where = "WHERE " + " AND ".join(conditions) if conditions else ""
520
+ sql = f"""SELECT * FROM user_playbooks up
521
+ {base_where}
522
+ ORDER BY up.created_at DESC"""
523
+ rows = self._fetchall(sql, params)
524
+ rows = _vector_rank_rows(rows, query_embedding, match_count)
525
+ return [_row_to_user_playbook(r) for r in rows]
526
+
527
+ # No query text, no embedding -- recency fallback
528
+ base_where = "WHERE " + " AND ".join(conditions) if conditions else "WHERE 1=1"
529
+ sql = f"""SELECT * FROM user_playbooks up
530
+ {base_where}
531
+ ORDER BY up.created_at DESC LIMIT ?"""
532
+ params.append(match_count)
533
+ rows = self._fetchall(sql, params)
534
+ return [_row_to_user_playbook(r) for r in rows]
535
+
536
+ @SQLiteStorageBase.handle_exceptions
537
+ def get_user_playbook_by_id(self, user_playbook_id: int) -> UserPlaybook | None:
538
+ row = self._fetchone(
539
+ "SELECT * FROM user_playbooks WHERE user_playbook_id = ?",
540
+ (user_playbook_id,),
541
+ )
542
+ return _row_to_user_playbook(row) if row else None
543
+
544
+ @SQLiteStorageBase.handle_exceptions
545
+ def get_user_playbooks_by_ids_any_user(
546
+ self,
547
+ user_playbook_ids: list[int],
548
+ status_filter: list[Status | None] | None = None,
549
+ ) -> list[UserPlaybook]:
550
+ if not user_playbook_ids:
551
+ return []
552
+ ph = ",".join("?" for _ in user_playbook_ids)
553
+ sql = f"SELECT * FROM user_playbooks WHERE user_playbook_id IN ({ph})" # noqa: S608
554
+ params: list[Any] = list(user_playbook_ids)
555
+ if status_filter is not None:
556
+ frag, sparams = _build_status_sql(status_filter)
557
+ sql += f" AND {frag}"
558
+ params.extend(sparams)
559
+ rows = self._fetchall(sql, params)
560
+ by_id = {
561
+ _row_to_user_playbook(row).user_playbook_id: _row_to_user_playbook(row)
562
+ for row in rows
563
+ }
564
+ return [by_id[upid] for upid in user_playbook_ids if upid in by_id]
565
+
566
+ # ------------------------------------------------------------------
567
+ # Agent Playbook methods
568
+ # ------------------------------------------------------------------
569
+
570
+ @SQLiteStorageBase.handle_exceptions
571
+ def save_agent_playbooks(
572
+ self, agent_playbooks: list[AgentPlaybook]
573
+ ) -> list[AgentPlaybook]:
574
+ saved: list[AgentPlaybook] = []
575
+ for ap in agent_playbooks:
576
+ embedding_text = ap.trigger or ap.content
577
+ if self._should_expand_documents():
578
+ with ThreadPoolExecutor(max_workers=2) as executor:
579
+ emb_future = executor.submit(self._get_embedding, embedding_text)
580
+ exp_future = executor.submit(self._expand_document, embedding_text)
581
+ ap.embedding = emb_future.result(timeout=15)
582
+ ap.expanded_terms = exp_future.result(timeout=15)
583
+ else:
584
+ ap.embedding = self._get_embedding(embedding_text)
585
+
586
+ created_at_iso = _epoch_to_iso(ap.created_at)
587
+ with self._lock:
588
+ cur = self.conn.execute(
589
+ """INSERT INTO agent_playbooks
590
+ (playbook_name, created_at, agent_version, content,
591
+ trigger, rationale, blocking_issue,
592
+ playbook_status, playbook_metadata, embedding,
593
+ expanded_terms, status)
594
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?)""",
595
+ (
596
+ ap.playbook_name,
597
+ created_at_iso,
598
+ ap.agent_version,
599
+ ap.content,
600
+ ap.trigger,
601
+ ap.rationale,
602
+ json.dumps(ap.blocking_issue.model_dump())
603
+ if ap.blocking_issue
604
+ else None,
605
+ ap.playbook_status.value
606
+ if isinstance(ap.playbook_status, PlaybookStatus)
607
+ else ap.playbook_status,
608
+ ap.playbook_metadata,
609
+ _json_dumps(ap.embedding),
610
+ ap.expanded_terms,
611
+ ap.status.value if ap.status else None,
612
+ ),
613
+ )
614
+ ap.agent_playbook_id = cur.lastrowid or 0
615
+ self.conn.commit()
616
+
617
+ fts_parts = [ap.trigger or "", ap.content or ""]
618
+ if ap.expanded_terms:
619
+ fts_parts.append(ap.expanded_terms)
620
+ self._fts_upsert(
621
+ "agent_playbooks_fts",
622
+ ap.agent_playbook_id,
623
+ search_text=" ".join(p for p in fts_parts if p) or "",
624
+ )
625
+ if ap.embedding:
626
+ self._vec_upsert(
627
+ "agent_playbooks_vec", ap.agent_playbook_id, ap.embedding
628
+ )
629
+ saved.append(ap)
630
+ return saved
631
+
632
+ @SQLiteStorageBase.handle_exceptions
633
+ def get_agent_playbooks(
634
+ self,
635
+ limit: int = 100,
636
+ playbook_name: str | None = None,
637
+ agent_version: str | None = None,
638
+ status_filter: list[Status | None] | None = None,
639
+ playbook_status_filter: list[PlaybookStatus] | None = None,
640
+ ) -> list[AgentPlaybook]:
641
+ sql = "SELECT * FROM agent_playbooks WHERE 1=1"
642
+ params: list[Any] = []
643
+
644
+ if playbook_name:
645
+ sql += " AND playbook_name = ?"
646
+ params.append(playbook_name)
647
+
648
+ if agent_version is not None:
649
+ sql += " AND agent_version = ?"
650
+ params.append(agent_version)
651
+
652
+ if status_filter is not None:
653
+ frag, sparams = _build_status_sql(status_filter)
654
+ sql += f" AND {frag}"
655
+ params.extend(sparams)
656
+ else:
657
+ sql += " AND status IS NULL"
658
+
659
+ if playbook_status_filter:
660
+ ph = ",".join("?" for _ in playbook_status_filter)
661
+ sql += f" AND playbook_status IN ({ph})"
662
+ params.extend(ps.value for ps in playbook_status_filter)
663
+
664
+ sql += " ORDER BY created_at DESC LIMIT ?"
665
+ params.append(limit)
666
+ rows = self._fetchall(sql, params)
667
+ return [_row_to_agent_playbook(r) for r in rows]
668
+
669
+ @SQLiteStorageBase.handle_exceptions
670
+ def get_agent_playbook_by_id(self, agent_playbook_id: int) -> AgentPlaybook | None:
671
+ row = self._fetchone(
672
+ "SELECT * FROM agent_playbooks WHERE agent_playbook_id = ?",
673
+ (agent_playbook_id,),
674
+ )
675
+ return _row_to_agent_playbook(row) if row else None
676
+
677
+ @SQLiteStorageBase.handle_exceptions
678
+ def delete_all_agent_playbooks(self) -> None:
679
+ with self._lock:
680
+ self.conn.execute("DELETE FROM agent_playbooks_fts")
681
+ self.conn.execute("DELETE FROM agent_playbooks")
682
+ self.conn.commit()
683
+
684
+ @SQLiteStorageBase.handle_exceptions
685
+ def delete_agent_playbook(self, agent_playbook_id: int) -> None:
686
+ self._fts_delete("agent_playbooks_fts", agent_playbook_id)
687
+ self._vec_delete("agent_playbooks_vec", agent_playbook_id)
688
+ self._execute(
689
+ "DELETE FROM agent_playbooks WHERE agent_playbook_id = ?",
690
+ (agent_playbook_id,),
691
+ )
692
+
693
+ @SQLiteStorageBase.handle_exceptions
694
+ def delete_all_agent_playbooks_by_playbook_name(
695
+ self, playbook_name: str, agent_version: str | None = None
696
+ ) -> None:
697
+ sql = "SELECT agent_playbook_id FROM agent_playbooks WHERE playbook_name = ?"
698
+ params: list[Any] = [playbook_name]
699
+ if agent_version is not None:
700
+ sql += " AND agent_version = ?"
701
+ params.append(agent_version)
702
+ ids = [r["agent_playbook_id"] for r in self._fetchall(sql, params)]
703
+ if ids:
704
+ ph = ",".join("?" for _ in ids)
705
+ with self._lock:
706
+ self.conn.execute(
707
+ f"DELETE FROM agent_playbooks_fts WHERE rowid IN ({ph})", ids
708
+ )
709
+ self.conn.commit()
710
+
711
+ del_sql = "DELETE FROM agent_playbooks WHERE playbook_name = ?"
712
+ del_params: list[Any] = [playbook_name]
713
+ if agent_version is not None:
714
+ del_sql += " AND agent_version = ?"
715
+ del_params.append(agent_version)
716
+ self._execute(del_sql, del_params)
717
+
718
+ @SQLiteStorageBase.handle_exceptions
719
+ def delete_agent_playbooks_by_ids(self, agent_playbook_ids: list[int]) -> None:
720
+ if not agent_playbook_ids:
721
+ return
722
+ ph = ",".join("?" for _ in agent_playbook_ids)
723
+ with self._lock:
724
+ self.conn.execute(
725
+ f"DELETE FROM agent_playbooks_fts WHERE rowid IN ({ph})",
726
+ agent_playbook_ids,
727
+ )
728
+ self.conn.execute(
729
+ f"DELETE FROM agent_playbooks WHERE agent_playbook_id IN ({ph})",
730
+ agent_playbook_ids,
731
+ )
732
+ self.conn.commit()
733
+
734
+ @SQLiteStorageBase.handle_exceptions
735
+ def update_agent_playbook_status(
736
+ self, agent_playbook_id: int, playbook_status: PlaybookStatus
737
+ ) -> None:
738
+ row = self._fetchone(
739
+ "SELECT agent_playbook_id FROM agent_playbooks WHERE agent_playbook_id = ?",
740
+ (agent_playbook_id,),
741
+ )
742
+ if not row:
743
+ raise ValueError(f"Agent playbook with ID {agent_playbook_id} not found")
744
+ self._execute(
745
+ "UPDATE agent_playbooks SET playbook_status = ? WHERE agent_playbook_id = ?",
746
+ (playbook_status.value, agent_playbook_id),
747
+ )
748
+
749
+ @SQLiteStorageBase.handle_exceptions
750
+ def update_agent_playbook(
751
+ self,
752
+ agent_playbook_id: int,
753
+ playbook_name: str | None = None,
754
+ content: str | None = None,
755
+ trigger: str | None = None,
756
+ rationale: str | None = None,
757
+ blocking_issue: BlockingIssue | None = None,
758
+ playbook_status: PlaybookStatus | None = None,
759
+ ) -> None:
760
+ row = self._fetchone(
761
+ "SELECT agent_playbook_id FROM agent_playbooks WHERE agent_playbook_id = ?",
762
+ (agent_playbook_id,),
763
+ )
764
+ if not row:
765
+ raise ValueError(f"Agent playbook with ID {agent_playbook_id} not found")
766
+ updates: list[str] = []
767
+ params: list[Any] = []
768
+ if playbook_name is not None:
769
+ updates.append("playbook_name = ?")
770
+ params.append(playbook_name)
771
+ if content is not None:
772
+ updates.append("content = ?")
773
+ params.append(content)
774
+ if trigger is not None:
775
+ updates.append("trigger = ?")
776
+ params.append(trigger)
777
+ if rationale is not None:
778
+ updates.append("rationale = ?")
779
+ params.append(rationale)
780
+ if blocking_issue is not None:
781
+ updates.append("blocking_issue = ?")
782
+ params.append(json.dumps(blocking_issue.model_dump()))
783
+ if playbook_status is not None:
784
+ updates.append("playbook_status = ?")
785
+ params.append(playbook_status.value)
786
+ if updates:
787
+ params.append(agent_playbook_id)
788
+ self._execute(
789
+ f"UPDATE agent_playbooks SET {', '.join(updates)} WHERE agent_playbook_id = ?",
790
+ tuple(params),
791
+ )
792
+
793
+ @SQLiteStorageBase.handle_exceptions
794
+ def update_user_playbook(
795
+ self,
796
+ user_playbook_id: int,
797
+ playbook_name: str | None = None,
798
+ content: str | None = None,
799
+ trigger: str | None = None,
800
+ rationale: str | None = None,
801
+ blocking_issue: BlockingIssue | None = None,
802
+ ) -> None:
803
+ row = self._fetchone(
804
+ "SELECT user_playbook_id FROM user_playbooks WHERE user_playbook_id = ?",
805
+ (user_playbook_id,),
806
+ )
807
+ if not row:
808
+ raise ValueError(f"User playbook with ID {user_playbook_id} not found")
809
+ updates: list[str] = []
810
+ params: list[Any] = []
811
+ if playbook_name is not None:
812
+ updates.append("playbook_name = ?")
813
+ params.append(playbook_name)
814
+ if content is not None:
815
+ updates.append("content = ?")
816
+ params.append(content)
817
+ if trigger is not None:
818
+ updates.append("trigger = ?")
819
+ params.append(trigger)
820
+ if rationale is not None:
821
+ updates.append("rationale = ?")
822
+ params.append(rationale)
823
+ if blocking_issue is not None:
824
+ updates.append("blocking_issue = ?")
825
+ params.append(json.dumps(blocking_issue.model_dump()))
826
+ if updates:
827
+ params.append(user_playbook_id)
828
+ self._execute(
829
+ f"UPDATE user_playbooks SET {', '.join(updates)} WHERE user_playbook_id = ?",
830
+ tuple(params),
831
+ )
832
+
833
+ @SQLiteStorageBase.handle_exceptions
834
+ def archive_agent_playbooks_by_playbook_name(
835
+ self, playbook_name: str, agent_version: str | None = None
836
+ ) -> None:
837
+ sql = "UPDATE agent_playbooks SET status = 'archived' WHERE playbook_name = ? AND playbook_status != ?"
838
+ params: list[Any] = [playbook_name, PlaybookStatus.APPROVED.value]
839
+ if agent_version is not None:
840
+ sql += " AND agent_version = ?"
841
+ params.append(agent_version)
842
+ self._execute(sql, params)
843
+
844
+ @SQLiteStorageBase.handle_exceptions
845
+ def archive_agent_playbooks_by_ids(self, agent_playbook_ids: list[int]) -> None:
846
+ if not agent_playbook_ids:
847
+ return
848
+ ph = ",".join("?" for _ in agent_playbook_ids)
849
+ self._execute(
850
+ f"UPDATE agent_playbooks SET status = 'archived' WHERE agent_playbook_id IN ({ph}) AND playbook_status != ?",
851
+ [*agent_playbook_ids, PlaybookStatus.APPROVED.value],
852
+ )
853
+
854
+ @SQLiteStorageBase.handle_exceptions
855
+ def restore_archived_agent_playbooks_by_playbook_name(
856
+ self, playbook_name: str, agent_version: str | None = None
857
+ ) -> None:
858
+ sql = "UPDATE agent_playbooks SET status = NULL WHERE playbook_name = ? AND status = 'archived'"
859
+ params: list[Any] = [playbook_name]
860
+ if agent_version is not None:
861
+ sql += " AND agent_version = ?"
862
+ params.append(agent_version)
863
+ self._execute(sql, params)
864
+
865
+ @SQLiteStorageBase.handle_exceptions
866
+ def restore_archived_agent_playbooks_by_ids(
867
+ self, agent_playbook_ids: list[int]
868
+ ) -> None:
869
+ if not agent_playbook_ids:
870
+ return
871
+ ph = ",".join("?" for _ in agent_playbook_ids)
872
+ self._execute(
873
+ f"UPDATE agent_playbooks SET status = NULL WHERE agent_playbook_id IN ({ph}) AND status = 'archived'",
874
+ agent_playbook_ids,
875
+ )
876
+
877
+ # ------------------------------------------------------------------
878
+ # Playbook optimizer methods
879
+ # ------------------------------------------------------------------
880
+
881
+ @SQLiteStorageBase.handle_exceptions
882
+ def set_source_user_playbook_ids_for_agent_playbook(
883
+ self, agent_playbook_id: int, user_playbook_ids: list[int]
884
+ ) -> None:
885
+ self.set_source_windows_for_agent_playbook(
886
+ agent_playbook_id,
887
+ [
888
+ AgentPlaybookSourceWindow(
889
+ user_playbook_id=upid, source_interaction_ids=[]
890
+ )
891
+ for upid in user_playbook_ids
892
+ ],
893
+ )
894
+
895
+ @SQLiteStorageBase.handle_exceptions
896
+ def get_source_user_playbook_ids_for_agent_playbook(
897
+ self, agent_playbook_id: int
898
+ ) -> list[int]:
899
+ return [
900
+ window.user_playbook_id
901
+ for window in self.get_source_windows_for_agent_playbook(agent_playbook_id)
902
+ ]
903
+
904
+ @SQLiteStorageBase.handle_exceptions
905
+ def set_source_windows_for_agent_playbook(
906
+ self,
907
+ agent_playbook_id: int,
908
+ source_windows: list[AgentPlaybookSourceWindow],
909
+ ) -> None:
910
+ by_id: dict[int, list[int]] = {}
911
+ for window in source_windows:
912
+ ids = by_id.setdefault(window.user_playbook_id, [])
913
+ seen = set(ids)
914
+ for source_id in window.source_interaction_ids:
915
+ if source_id not in seen:
916
+ ids.append(source_id)
917
+ seen.add(source_id)
918
+ with self._lock:
919
+ self.conn.execute(
920
+ "DELETE FROM agent_playbook_source_user_playbooks WHERE agent_playbook_id = ?",
921
+ (agent_playbook_id,),
922
+ )
923
+ self.conn.executemany(
924
+ """INSERT OR IGNORE INTO agent_playbook_source_user_playbooks
925
+ (agent_playbook_id, user_playbook_id, source_interaction_ids)
926
+ VALUES (?, ?, ?)""",
927
+ [
928
+ (
929
+ agent_playbook_id,
930
+ upid,
931
+ _json_dumps(source_interaction_ids) or "[]",
932
+ )
933
+ for upid, source_interaction_ids in by_id.items()
934
+ ],
935
+ )
936
+ self.conn.commit()
937
+
938
+ @SQLiteStorageBase.handle_exceptions
939
+ def get_source_windows_for_agent_playbook(
940
+ self, agent_playbook_id: int
941
+ ) -> list[AgentPlaybookSourceWindow]:
942
+ rows = self._fetchall(
943
+ """SELECT user_playbook_id, source_interaction_ids
944
+ FROM agent_playbook_source_user_playbooks
945
+ WHERE agent_playbook_id = ?
946
+ ORDER BY user_playbook_id ASC""",
947
+ (agent_playbook_id,),
948
+ )
949
+ return [
950
+ AgentPlaybookSourceWindow(
951
+ user_playbook_id=int(row["user_playbook_id"]),
952
+ source_interaction_ids=_json_loads(row["source_interaction_ids"]) or [],
953
+ )
954
+ for row in rows
955
+ ]
956
+
957
+ @SQLiteStorageBase.handle_exceptions
958
+ def create_playbook_optimization_job(
959
+ self, job: PlaybookOptimizationJob
960
+ ) -> PlaybookOptimizationJob:
961
+ with self._lock:
962
+ cur = self.conn.execute(
963
+ """INSERT INTO playbook_optimization_jobs
964
+ (target_kind, target_id, status, best_candidate_id,
965
+ successor_target_id, decision_reason, metadata_json,
966
+ created_at, updated_at)
967
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
968
+ (
969
+ job.target_kind,
970
+ job.target_id,
971
+ job.status,
972
+ job.best_candidate_id,
973
+ job.successor_target_id,
974
+ job.decision_reason,
975
+ job.metadata_json,
976
+ job.created_at,
977
+ job.updated_at,
978
+ ),
979
+ )
980
+ job.job_id = cur.lastrowid or 0
981
+ self.conn.commit()
982
+ return job
983
+
984
+ @SQLiteStorageBase.handle_exceptions
985
+ def update_playbook_optimization_job(
986
+ self,
987
+ job_id: int,
988
+ *,
989
+ status: str | None = None,
990
+ best_candidate_id: int | None = None,
991
+ successor_target_id: int | None = None,
992
+ decision_reason: str | None = None,
993
+ metadata_json: str | None = None,
994
+ ) -> None:
995
+ updates: list[str] = ["updated_at = strftime('%s','now')"]
996
+ params: list[Any] = []
997
+ if status is not None:
998
+ updates.append("status = ?")
999
+ params.append(status)
1000
+ if best_candidate_id is not None:
1001
+ updates.append("best_candidate_id = ?")
1002
+ params.append(best_candidate_id)
1003
+ if successor_target_id is not None:
1004
+ updates.append("successor_target_id = ?")
1005
+ params.append(successor_target_id)
1006
+ if decision_reason is not None:
1007
+ updates.append("decision_reason = ?")
1008
+ params.append(decision_reason)
1009
+ if metadata_json is not None:
1010
+ updates.append("metadata_json = ?")
1011
+ params.append(metadata_json)
1012
+ params.append(job_id)
1013
+ self._execute(
1014
+ f"UPDATE playbook_optimization_jobs SET {', '.join(updates)} WHERE job_id = ?", # noqa: S608
1015
+ tuple(params),
1016
+ )
1017
+
1018
+ @SQLiteStorageBase.handle_exceptions
1019
+ def insert_playbook_optimization_candidate(
1020
+ self, candidate: PlaybookOptimizationCandidate
1021
+ ) -> PlaybookOptimizationCandidate:
1022
+ with self._lock:
1023
+ cur = self.conn.execute(
1024
+ """INSERT INTO playbook_optimization_candidates
1025
+ (job_id, candidate_index, content, parent_candidate_ids,
1026
+ aggregate_score, is_winner, created_at)
1027
+ VALUES (?, ?, ?, ?, ?, ?, ?)""",
1028
+ (
1029
+ candidate.job_id,
1030
+ candidate.candidate_index,
1031
+ candidate.content,
1032
+ _json_dumps(candidate.parent_candidate_ids) or "[]",
1033
+ candidate.aggregate_score,
1034
+ 1 if candidate.is_winner else 0,
1035
+ candidate.created_at,
1036
+ ),
1037
+ )
1038
+ candidate.candidate_id = cur.lastrowid or 0
1039
+ self.conn.commit()
1040
+ return candidate
1041
+
1042
+ @SQLiteStorageBase.handle_exceptions
1043
+ def list_playbook_optimization_candidates(
1044
+ self, job_id: int
1045
+ ) -> list[PlaybookOptimizationCandidate]:
1046
+ rows = self._fetchall(
1047
+ "SELECT * FROM playbook_optimization_candidates WHERE job_id = ? ORDER BY candidate_id ASC",
1048
+ (job_id,),
1049
+ )
1050
+ return [_row_to_playbook_optimization_candidate(row) for row in rows]
1051
+
1052
+ @SQLiteStorageBase.handle_exceptions
1053
+ def update_playbook_optimization_candidate(
1054
+ self,
1055
+ candidate_id: int,
1056
+ *,
1057
+ aggregate_score: float | None = None,
1058
+ is_winner: bool | None = None,
1059
+ ) -> None:
1060
+ updates: list[str] = []
1061
+ params: list[Any] = []
1062
+ if aggregate_score is not None:
1063
+ updates.append("aggregate_score = ?")
1064
+ params.append(aggregate_score)
1065
+ if is_winner is not None:
1066
+ updates.append("is_winner = ?")
1067
+ params.append(1 if is_winner else 0)
1068
+ if not updates:
1069
+ return
1070
+ params.append(candidate_id)
1071
+ self._execute(
1072
+ f"UPDATE playbook_optimization_candidates SET {', '.join(updates)} WHERE candidate_id = ?", # noqa: S608
1073
+ tuple(params),
1074
+ )
1075
+
1076
+ @SQLiteStorageBase.handle_exceptions
1077
+ def insert_playbook_optimization_evaluation(
1078
+ self, evaluation: PlaybookOptimizationEvaluation
1079
+ ) -> PlaybookOptimizationEvaluation:
1080
+ with self._lock:
1081
+ cur = self.conn.execute(
1082
+ """INSERT INTO playbook_optimization_evaluations
1083
+ (job_id, candidate_id, target_kind, target_id,
1084
+ scenario_user_playbook_id, source_interaction_ids, score,
1085
+ verdict, likert, rationale, asi_json, incumbent_rollout_json,
1086
+ candidate_rollout_json, created_at)
1087
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
1088
+ (
1089
+ evaluation.job_id,
1090
+ evaluation.candidate_id,
1091
+ evaluation.target_kind,
1092
+ evaluation.target_id,
1093
+ evaluation.scenario_user_playbook_id,
1094
+ _json_dumps(evaluation.source_interaction_ids) or "[]",
1095
+ evaluation.score,
1096
+ evaluation.verdict,
1097
+ evaluation.likert,
1098
+ evaluation.rationale,
1099
+ evaluation.asi_json,
1100
+ evaluation.incumbent_rollout_json,
1101
+ evaluation.candidate_rollout_json,
1102
+ evaluation.created_at,
1103
+ ),
1104
+ )
1105
+ evaluation.evaluation_id = cur.lastrowid or 0
1106
+ self.conn.commit()
1107
+ return evaluation
1108
+
1109
+ @SQLiteStorageBase.handle_exceptions
1110
+ def list_playbook_optimization_evaluations(
1111
+ self, job_id: int
1112
+ ) -> list[PlaybookOptimizationEvaluation]:
1113
+ rows = self._fetchall(
1114
+ "SELECT * FROM playbook_optimization_evaluations WHERE job_id = ? ORDER BY evaluation_id ASC",
1115
+ (job_id,),
1116
+ )
1117
+ return [_row_to_playbook_optimization_evaluation(row) for row in rows]
1118
+
1119
+ @SQLiteStorageBase.handle_exceptions
1120
+ def insert_playbook_optimization_event(
1121
+ self, event: PlaybookOptimizationEvent
1122
+ ) -> PlaybookOptimizationEvent:
1123
+ with self._lock:
1124
+ cur = self.conn.execute(
1125
+ """INSERT INTO playbook_optimization_events
1126
+ (job_id, event_type, payload_json, created_at)
1127
+ VALUES (?, ?, ?, ?)""",
1128
+ (event.job_id, event.event_type, event.payload_json, event.created_at),
1129
+ )
1130
+ event.event_id = cur.lastrowid or 0
1131
+ self.conn.commit()
1132
+ return event
1133
+
1134
+ @SQLiteStorageBase.handle_exceptions
1135
+ def delete_archived_agent_playbooks_by_playbook_name(
1136
+ self, playbook_name: str, agent_version: str | None = None
1137
+ ) -> None:
1138
+ # Get IDs for FTS cleanup
1139
+ sql = "SELECT agent_playbook_id FROM agent_playbooks WHERE playbook_name = ? AND status = 'archived'"
1140
+ params: list[Any] = [playbook_name]
1141
+ if agent_version is not None:
1142
+ sql += " AND agent_version = ?"
1143
+ params.append(agent_version)
1144
+ ids = [r["agent_playbook_id"] for r in self._fetchall(sql, params)]
1145
+ if ids:
1146
+ ph = ",".join("?" for _ in ids)
1147
+ with self._lock:
1148
+ self.conn.execute(
1149
+ f"DELETE FROM agent_playbooks_fts WHERE rowid IN ({ph})", ids
1150
+ )
1151
+ self.conn.commit()
1152
+
1153
+ del_sql = "DELETE FROM agent_playbooks WHERE playbook_name = ? AND status = 'archived'"
1154
+ del_params: list[Any] = [playbook_name]
1155
+ if agent_version is not None:
1156
+ del_sql += " AND agent_version = ?"
1157
+ del_params.append(agent_version)
1158
+ self._execute(del_sql, del_params)
1159
+
1160
+ @SQLiteStorageBase.handle_exceptions
1161
+ def search_agent_playbooks( # noqa: C901
1162
+ self,
1163
+ request: SearchAgentPlaybookRequest,
1164
+ options: SearchOptions | None = None,
1165
+ ) -> list[AgentPlaybook]:
1166
+ query = request.query
1167
+ agent_version = request.agent_version
1168
+ playbook_name = request.playbook_name
1169
+ start_time = int(request.start_time.timestamp()) if request.start_time else None
1170
+ end_time = int(request.end_time.timestamp()) if request.end_time else None
1171
+ status_filter = request.status_filter
1172
+ playbook_status_filter = request.playbook_status_filter
1173
+ match_count = request.top_k or 10
1174
+ query_embedding = options.query_embedding if options else None
1175
+ mode = _effective_search_mode(
1176
+ request.search_mode, query_embedding, request.query
1177
+ )
1178
+ rrf_k = options.rrf_k if options else 60
1179
+ vector_weight = options.vector_weight if options else 1.0
1180
+ fts_weight = options.fts_weight if options else 1.0
1181
+
1182
+ conditions: list[str] = []
1183
+ params: list[Any] = []
1184
+
1185
+ if agent_version:
1186
+ conditions.append("ap.agent_version = ?")
1187
+ params.append(agent_version)
1188
+ if playbook_name:
1189
+ conditions.append("ap.playbook_name = ?")
1190
+ params.append(playbook_name)
1191
+ if start_time:
1192
+ conditions.append("ap.created_at >= ?")
1193
+ params.append(_epoch_to_iso(start_time))
1194
+ if end_time:
1195
+ conditions.append("ap.created_at <= ?")
1196
+ params.append(_epoch_to_iso(end_time))
1197
+ if playbook_status_filter is not None:
1198
+ if isinstance(playbook_status_filter, list):
1199
+ if not playbook_status_filter:
1200
+ conditions.append("1=0")
1201
+ else:
1202
+ placeholders = ",".join("?" for _ in playbook_status_filter)
1203
+ conditions.append(f"ap.playbook_status IN ({placeholders})")
1204
+ params.extend(s.value for s in playbook_status_filter)
1205
+ else:
1206
+ conditions.append("ap.playbook_status = ?")
1207
+ params.append(playbook_status_filter.value)
1208
+ if status_filter is not None:
1209
+ frag, sparams = _build_status_sql(status_filter)
1210
+ conditions.append(frag)
1211
+ params.extend(sparams)
1212
+
1213
+ where_extra = (" AND " + " AND ".join(conditions)) if conditions else ""
1214
+ overfetch = match_count * 5 if mode != SearchMode.FTS else match_count
1215
+
1216
+ # Pure vector search: fetch all candidates, rank by cosine similarity
1217
+ if mode == SearchMode.VECTOR and query_embedding:
1218
+ base_where = "WHERE " + " AND ".join(conditions) if conditions else ""
1219
+ sql = f"""SELECT * FROM agent_playbooks ap
1220
+ {base_where}
1221
+ ORDER BY ap.created_at DESC"""
1222
+ rows = self._fetchall(sql, params)
1223
+ rows = _vector_rank_rows(rows, query_embedding, match_count)
1224
+ return [_row_to_agent_playbook(r) for r in rows]
1225
+
1226
+ if query:
1227
+ fts_query = _sanitize_fts_query(query)
1228
+ sql = f"""SELECT ap.* FROM agent_playbooks ap
1229
+ JOIN agent_playbooks_fts f ON ap.agent_playbook_id = f.rowid
1230
+ WHERE agent_playbooks_fts MATCH ?{where_extra}
1231
+ ORDER BY bm25(agent_playbooks_fts, 1.0)
1232
+ LIMIT ?"""
1233
+ fts_rows = self._fetchall(sql, [fts_query, *params, overfetch])
1234
+
1235
+ if mode == SearchMode.HYBRID and query_embedding:
1236
+ base_where = "WHERE " + " AND ".join(conditions) if conditions else ""
1237
+ vec_limit = match_count * 10
1238
+ vec_sql = f"""SELECT * FROM agent_playbooks ap
1239
+ {base_where}
1240
+ ORDER BY ap.created_at DESC
1241
+ LIMIT ?"""
1242
+ vec_candidates = self._fetchall(vec_sql, [*params, vec_limit])
1243
+ vec_rows = _vector_rank_rows(vec_candidates, query_embedding, overfetch)
1244
+ rows = _true_rrf_merge(
1245
+ fts_rows,
1246
+ vec_rows,
1247
+ "agent_playbook_id",
1248
+ match_count,
1249
+ rrf_k,
1250
+ vector_weight,
1251
+ fts_weight,
1252
+ )
1253
+ return [_row_to_agent_playbook(r) for r in rows]
1254
+ return [_row_to_agent_playbook(r) for r in fts_rows[:match_count]]
1255
+
1256
+ # HYBRID without query text: rank by embedding only
1257
+ if query_embedding:
1258
+ base_where = "WHERE " + " AND ".join(conditions) if conditions else ""
1259
+ sql = f"""SELECT * FROM agent_playbooks ap
1260
+ {base_where}
1261
+ ORDER BY ap.created_at DESC"""
1262
+ rows = self._fetchall(sql, params)
1263
+ rows = _vector_rank_rows(rows, query_embedding, match_count)
1264
+ return [_row_to_agent_playbook(r) for r in rows]
1265
+
1266
+ # No query text, no embedding -- recency fallback
1267
+ base_where = "WHERE " + " AND ".join(conditions) if conditions else "WHERE 1=1"
1268
+ sql = f"""SELECT * FROM agent_playbooks ap
1269
+ {base_where}
1270
+ ORDER BY ap.created_at DESC LIMIT ?"""
1271
+ params.append(match_count)
1272
+ rows = self._fetchall(sql, params)
1273
+ return [_row_to_agent_playbook(r) for r in rows]
1274
+
1275
+ # ------------------------------------------------------------------
1276
+ # Agent Success Evaluation methods
1277
+ # ------------------------------------------------------------------
1278
+
1279
+ @SQLiteStorageBase.handle_exceptions
1280
+ def save_agent_success_evaluation_results(
1281
+ self, results: list[AgentSuccessEvaluationResult]
1282
+ ) -> None:
1283
+ for result in results:
1284
+ embedding_text = f"{result.failure_type} {result.failure_reason}"
1285
+ if embedding_text.strip():
1286
+ result.embedding = self._get_embedding(embedding_text)
1287
+ else:
1288
+ result.embedding = []
1289
+
1290
+ created_at_iso = _epoch_to_iso(result.created_at)
1291
+ self._execute(
1292
+ """INSERT INTO agent_success_evaluation_result
1293
+ (session_id, agent_version, evaluation_name, is_success,
1294
+ failure_type, failure_reason, regular_vs_shadow,
1295
+ number_of_correction_per_session, user_turns_to_resolution,
1296
+ is_escalated, embedding, created_at)
1297
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?)""",
1298
+ (
1299
+ result.session_id,
1300
+ result.agent_version,
1301
+ result.evaluation_name,
1302
+ int(result.is_success),
1303
+ result.failure_type,
1304
+ result.failure_reason,
1305
+ result.regular_vs_shadow.value
1306
+ if result.regular_vs_shadow
1307
+ else None,
1308
+ result.number_of_correction_per_session,
1309
+ result.user_turns_to_resolution,
1310
+ int(result.is_escalated),
1311
+ _json_dumps(result.embedding) if result.embedding else None,
1312
+ created_at_iso,
1313
+ ),
1314
+ )
1315
+
1316
+ @SQLiteStorageBase.handle_exceptions
1317
+ def get_agent_success_evaluation_results(
1318
+ self, limit: int = 100, agent_version: str | None = None
1319
+ ) -> list[AgentSuccessEvaluationResult]:
1320
+ sql = "SELECT * FROM agent_success_evaluation_result"
1321
+ params: list[Any] = []
1322
+ if agent_version is not None:
1323
+ sql += " WHERE agent_version = ?"
1324
+ params.append(agent_version)
1325
+ sql += " ORDER BY created_at DESC LIMIT ?"
1326
+ params.append(limit)
1327
+ rows = self._fetchall(sql, params)
1328
+ return [_row_to_eval_result(r) for r in rows]
1329
+
1330
+ @SQLiteStorageBase.handle_exceptions
1331
+ def delete_all_agent_success_evaluation_results(self) -> None:
1332
+ self._execute("DELETE FROM agent_success_evaluation_result")
1333
+
1334
+ @SQLiteStorageBase.handle_exceptions
1335
+ def delete_agent_success_evaluation_results_for_session(
1336
+ self,
1337
+ session_id: str,
1338
+ evaluation_name: str,
1339
+ agent_version: str,
1340
+ ) -> int:
1341
+ """Delete results scoped to (session_id, evaluation_name, agent_version).
1342
+
1343
+ Args:
1344
+ session_id (str): Session whose results to clear.
1345
+ evaluation_name (str): Which evaluator's results to clear.
1346
+ agent_version (str): Agent version scope.
1347
+
1348
+ Returns:
1349
+ int: Number of rows deleted.
1350
+ """
1351
+ cur = self._execute(
1352
+ """DELETE FROM agent_success_evaluation_result
1353
+ WHERE session_id = ? AND evaluation_name = ? AND agent_version = ?""",
1354
+ (session_id, evaluation_name, agent_version),
1355
+ )
1356
+ return cur.rowcount
1357
+
1358
+ @SQLiteStorageBase.handle_exceptions
1359
+ def delete_agent_success_evaluation_results_by_ids(
1360
+ self, result_ids: list[int]
1361
+ ) -> int:
1362
+ """Delete agent success eval result rows by primary key.
1363
+
1364
+ Args:
1365
+ result_ids (list[int]): Primary-key result_ids to delete. An empty
1366
+ list is a no-op that returns 0.
1367
+
1368
+ Returns:
1369
+ int: Number of rows actually deleted (ignores non-existent ids).
1370
+ """
1371
+ if not result_ids:
1372
+ return 0
1373
+ placeholders = ",".join(["?"] * len(result_ids))
1374
+ cur = self._execute(
1375
+ f"DELETE FROM agent_success_evaluation_result WHERE result_id IN ({placeholders})",
1376
+ list(result_ids),
1377
+ )
1378
+ return cur.rowcount