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,1253 @@
1
+ """SQLite storage for resumable extraction agent runs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sqlite3
6
+ from datetime import UTC, datetime, timedelta
7
+ from typing import Any
8
+
9
+ from reflexio.server.services.storage.storage_base import (
10
+ AgentBinding,
11
+ AgentRunRecord,
12
+ AgentRunStatus,
13
+ PendingToolCallRecord,
14
+ PendingToolCallStatus,
15
+ PendingToolCallUpsertResult,
16
+ PriorAnswerMatch,
17
+ RunToolDependencyKind,
18
+ RunToolDependencyRecord,
19
+ embedding_similarity,
20
+ not_applicable_tool_result,
21
+ )
22
+
23
+ from ._base import SQLiteStorageBase, _json_dumps, _json_loads
24
+
25
+
26
+ def _dt(value: str | None) -> datetime | None:
27
+ if value is None:
28
+ return None
29
+ return datetime.fromisoformat(value)
30
+
31
+
32
+ def _dt_str(value: datetime | None) -> str | None:
33
+ if value is None:
34
+ return None
35
+ if value.tzinfo is None:
36
+ value = value.replace(tzinfo=UTC)
37
+ return value.astimezone(UTC).isoformat()
38
+
39
+
40
+ def _row_to_agent_run(row: sqlite3.Row) -> AgentRunRecord:
41
+ data = dict(row)
42
+ binding = AgentBinding(
43
+ org_id=data["org_id"],
44
+ extractor_kind=data["extractor_kind"],
45
+ user_id=data.get("user_id"),
46
+ request_id=data["request_id"],
47
+ agent_version=data.get("agent_version"),
48
+ source=data.get("source"),
49
+ source_interaction_ids=_json_loads(data.get("source_interaction_ids")) or [],
50
+ window_start_interaction_id=data.get("window_start_interaction_id"),
51
+ window_end_interaction_id=data.get("window_end_interaction_id"),
52
+ extractor_config_hash=data.get("extractor_config_hash"),
53
+ )
54
+ return AgentRunRecord(
55
+ id=data["id"],
56
+ binding=binding,
57
+ status=AgentRunStatus(data["status"]),
58
+ generation_request_snapshot=_json_loads(data.get("generation_request_snapshot"))
59
+ or {},
60
+ service_config_snapshot=_json_loads(data.get("service_config_snapshot")),
61
+ agent_context_snapshot=data.get("agent_context_snapshot"),
62
+ committed_output=_json_loads(data.get("committed_output")),
63
+ pending_tool_call_ids=_json_loads(data.get("pending_tool_call_ids")) or [],
64
+ max_steps_remaining=(
65
+ int(data["max_steps_remaining"])
66
+ if data.get("max_steps_remaining") is not None
67
+ else None
68
+ ),
69
+ resume_attempts=int(data.get("resume_attempts") or 0),
70
+ finalization_attempts=int(data.get("finalization_attempts") or 0),
71
+ next_resume_at=_dt(data.get("next_resume_at")),
72
+ claimed_by=data.get("claimed_by"),
73
+ claimed_at=_dt(data.get("claimed_at")),
74
+ agent_completed_at=_dt(data.get("agent_completed_at")),
75
+ finalized_at=_dt(data.get("finalized_at")),
76
+ created_at=_dt(data.get("created_at")),
77
+ updated_at=_dt(data.get("updated_at")),
78
+ expires_at=_dt(data.get("expires_at")),
79
+ last_error=data.get("last_error"),
80
+ )
81
+
82
+
83
+ def _row_to_pending_tool_call(row: sqlite3.Row) -> PendingToolCallRecord:
84
+ data = dict(row)
85
+ return PendingToolCallRecord(
86
+ id=data["id"],
87
+ org_id=data["org_id"],
88
+ scope=_json_loads(data.get("scope")) or {},
89
+ scope_hash=data["scope_hash"],
90
+ tool_name=data["tool_name"],
91
+ dedup_key=data["dedup_key"],
92
+ status=PendingToolCallStatus(data["status"]),
93
+ question_text=data["question_text"],
94
+ args=_json_loads(data.get("args")) or {},
95
+ tags=_json_loads(data.get("tags")) or [],
96
+ user_id=data.get("user_id"),
97
+ answer_format=data.get("answer_format"),
98
+ result=_json_loads(data.get("result")),
99
+ embedding=_json_loads(data.get("embedding")),
100
+ superseded_by=data.get("superseded_by"),
101
+ created_at=_dt(data.get("created_at")),
102
+ resolved_at=_dt(data.get("resolved_at")),
103
+ expires_at=_dt(data.get("expires_at")),
104
+ cache_until=_dt(data.get("cache_until")),
105
+ valid_until=_dt(data.get("valid_until")),
106
+ )
107
+
108
+
109
+ def _record_to_prior_answer_match(
110
+ record: PendingToolCallRecord,
111
+ *,
112
+ query_embedding: list[float] | None = None,
113
+ ) -> PriorAnswerMatch:
114
+ return PriorAnswerMatch(
115
+ pending_tool_call_id=record.id,
116
+ status=record.status,
117
+ question_text=record.question_text,
118
+ result=record.result,
119
+ valid_until=record.valid_until,
120
+ answer_format=record.answer_format,
121
+ created_at=record.created_at,
122
+ resolved_at=record.resolved_at,
123
+ expires_at=record.expires_at,
124
+ similarity=embedding_similarity(query_embedding, record.embedding),
125
+ )
126
+
127
+
128
+ def _row_to_run_tool_dependency(row: sqlite3.Row) -> RunToolDependencyRecord:
129
+ data = dict(row)
130
+ return RunToolDependencyRecord(
131
+ run_id=data["run_id"],
132
+ pending_tool_call_id=data["pending_tool_call_id"],
133
+ dependency_kind=RunToolDependencyKind(data["dependency_kind"]),
134
+ resolved_at=_dt(data.get("resolved_at")),
135
+ consumed_at=_dt(data.get("consumed_at")),
136
+ created_at=_dt(data.get("created_at")),
137
+ )
138
+
139
+
140
+ class SQLiteAgentRunMixin:
141
+ """SQLite-backed resumable extraction run storage."""
142
+
143
+ _lock: Any
144
+ conn: sqlite3.Connection
145
+ _fetchone: Any
146
+ _fetchall: Any
147
+ _current_timestamp: Any
148
+ org_id: str
149
+
150
+ def _finalize_runs_without_pending_dependencies_unlocked(self, now_s: str) -> None:
151
+ self.conn.execute(
152
+ """
153
+ UPDATE _agent_runs
154
+ SET status = ?,
155
+ finalized_at = COALESCE(finalized_at, ?),
156
+ updated_at = ?
157
+ WHERE status = ?
158
+ AND NOT EXISTS (
159
+ SELECT 1
160
+ FROM _run_tool_dependencies d
161
+ JOIN _pending_tool_calls p
162
+ ON p.id = d.pending_tool_call_id
163
+ WHERE d.run_id = _agent_runs.id
164
+ AND d.resolved_at IS NULL
165
+ AND d.consumed_at IS NULL
166
+ AND p.status = ?
167
+ )
168
+ """,
169
+ (
170
+ AgentRunStatus.FINALIZED.value,
171
+ now_s,
172
+ now_s,
173
+ AgentRunStatus.FINALIZED_PENDING_TOOL.value,
174
+ PendingToolCallStatus.PENDING.value,
175
+ ),
176
+ )
177
+
178
+ def _mark_runs_ready_with_actionable_dependencies_unlocked(
179
+ self, now_s: str, *, pending_tool_call_id: str
180
+ ) -> None:
181
+ self.conn.execute(
182
+ """
183
+ UPDATE _agent_runs
184
+ SET status = ?,
185
+ updated_at = ?
186
+ WHERE status IN (?, ?)
187
+ AND EXISTS (
188
+ SELECT 1
189
+ FROM _run_tool_dependencies changed
190
+ WHERE changed.run_id = _agent_runs.id
191
+ AND changed.pending_tool_call_id = ?
192
+ )
193
+ AND EXISTS (
194
+ SELECT 1
195
+ FROM _run_tool_dependencies d
196
+ JOIN _pending_tool_calls p
197
+ ON p.id = d.pending_tool_call_id
198
+ WHERE d.run_id = _agent_runs.id
199
+ AND d.resolved_at IS NOT NULL
200
+ AND d.consumed_at IS NULL
201
+ AND p.status = ?
202
+ AND COALESCE(json_extract(p.result, '$.not_applicable'), 0) != 1
203
+ )
204
+ """,
205
+ (
206
+ AgentRunStatus.RESUME_READY.value,
207
+ now_s,
208
+ AgentRunStatus.FINALIZED.value,
209
+ AgentRunStatus.FINALIZED_PENDING_TOOL.value,
210
+ pending_tool_call_id,
211
+ PendingToolCallStatus.RESOLVED.value,
212
+ ),
213
+ )
214
+
215
+ def _finalize_runs_without_actionable_dependencies_unlocked(
216
+ self, now_s: str, *, pending_tool_call_id: str
217
+ ) -> None:
218
+ self.conn.execute(
219
+ """
220
+ UPDATE _agent_runs
221
+ SET status = ?,
222
+ finalized_at = COALESCE(finalized_at, ?),
223
+ updated_at = ?
224
+ WHERE status IN (?, ?)
225
+ AND EXISTS (
226
+ SELECT 1
227
+ FROM _run_tool_dependencies changed
228
+ WHERE changed.run_id = _agent_runs.id
229
+ AND changed.pending_tool_call_id = ?
230
+ )
231
+ AND NOT EXISTS (
232
+ SELECT 1
233
+ FROM _run_tool_dependencies d
234
+ JOIN _pending_tool_calls p
235
+ ON p.id = d.pending_tool_call_id
236
+ WHERE d.run_id = _agent_runs.id
237
+ AND d.resolved_at IS NULL
238
+ AND d.consumed_at IS NULL
239
+ AND p.status = ?
240
+ )
241
+ AND NOT EXISTS (
242
+ SELECT 1
243
+ FROM _run_tool_dependencies d
244
+ JOIN _pending_tool_calls p
245
+ ON p.id = d.pending_tool_call_id
246
+ WHERE d.run_id = _agent_runs.id
247
+ AND d.resolved_at IS NOT NULL
248
+ AND d.consumed_at IS NULL
249
+ AND p.status = ?
250
+ AND COALESCE(json_extract(p.result, '$.not_applicable'), 0) != 1
251
+ )
252
+ """,
253
+ (
254
+ AgentRunStatus.FINALIZED.value,
255
+ now_s,
256
+ now_s,
257
+ AgentRunStatus.FINALIZED_PENDING_TOOL.value,
258
+ AgentRunStatus.RESUME_READY.value,
259
+ pending_tool_call_id,
260
+ PendingToolCallStatus.PENDING.value,
261
+ PendingToolCallStatus.RESOLVED.value,
262
+ ),
263
+ )
264
+
265
+ @SQLiteStorageBase.handle_exceptions
266
+ def create_agent_run(self, record: AgentRunRecord) -> AgentRunRecord:
267
+ binding = record.binding
268
+ with self._lock:
269
+ self.conn.execute(
270
+ """
271
+ INSERT INTO _agent_runs (
272
+ id, org_id, extractor_kind, user_id,
273
+ request_id, agent_version, source, source_interaction_ids,
274
+ window_start_interaction_id, window_end_interaction_id,
275
+ extractor_config_hash, status, generation_request_snapshot,
276
+ service_config_snapshot, agent_context_snapshot,
277
+ committed_output, pending_tool_call_ids, max_steps_remaining,
278
+ resume_attempts, finalization_attempts, next_resume_at,
279
+ claimed_by, claimed_at, agent_completed_at, finalized_at,
280
+ expires_at, last_error
281
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
282
+ """,
283
+ (
284
+ record.id,
285
+ binding.org_id,
286
+ binding.extractor_kind,
287
+ binding.user_id,
288
+ binding.request_id,
289
+ binding.agent_version,
290
+ binding.source,
291
+ _json_dumps(binding.source_interaction_ids),
292
+ binding.window_start_interaction_id,
293
+ binding.window_end_interaction_id,
294
+ binding.extractor_config_hash,
295
+ record.status.value,
296
+ _json_dumps(record.generation_request_snapshot),
297
+ _json_dumps(record.service_config_snapshot),
298
+ record.agent_context_snapshot,
299
+ _json_dumps(record.committed_output),
300
+ _json_dumps(record.pending_tool_call_ids),
301
+ record.max_steps_remaining,
302
+ record.resume_attempts,
303
+ record.finalization_attempts,
304
+ _dt_str(record.next_resume_at),
305
+ record.claimed_by,
306
+ _dt_str(record.claimed_at),
307
+ _dt_str(record.agent_completed_at),
308
+ _dt_str(record.finalized_at),
309
+ _dt_str(record.expires_at),
310
+ record.last_error,
311
+ ),
312
+ )
313
+ self.conn.commit()
314
+ stored = self.get_agent_run(record.id)
315
+ if stored is None: # pragma: no cover
316
+ raise RuntimeError(f"Failed to create agent run {record.id}")
317
+ return stored
318
+
319
+ @SQLiteStorageBase.handle_exceptions
320
+ def get_agent_run(self, run_id: str) -> AgentRunRecord | None:
321
+ row = self._fetchone("SELECT * FROM _agent_runs WHERE id = ?", (run_id,))
322
+ return _row_to_agent_run(row) if row else None
323
+
324
+ @SQLiteStorageBase.handle_exceptions
325
+ def update_agent_run_status(
326
+ self,
327
+ run_id: str,
328
+ status: AgentRunStatus,
329
+ *,
330
+ committed_output: dict[str, Any] | None = None,
331
+ pending_tool_call_ids: list[str] | None = None,
332
+ max_steps_remaining: int | None = None,
333
+ next_resume_at: datetime | None = None,
334
+ last_error: str | None = None,
335
+ increment_finalization_attempts: bool = False,
336
+ ) -> AgentRunRecord | None:
337
+ current_timestamp = self._current_timestamp()
338
+ assignments = ["status = ?", "updated_at = ?"]
339
+ params: list[Any] = [status.value, current_timestamp]
340
+ if committed_output is not None:
341
+ assignments.append("committed_output = ?")
342
+ params.append(_json_dumps(committed_output))
343
+ if pending_tool_call_ids is not None:
344
+ assignments.append("pending_tool_call_ids = ?")
345
+ params.append(_json_dumps(pending_tool_call_ids))
346
+ if max_steps_remaining is not None:
347
+ assignments.append("max_steps_remaining = ?")
348
+ params.append(max(0, max_steps_remaining))
349
+ if next_resume_at is not None:
350
+ assignments.append("next_resume_at = ?")
351
+ params.append(_dt_str(next_resume_at))
352
+ if last_error is not None:
353
+ assignments.append("last_error = ?")
354
+ params.append(last_error)
355
+ if increment_finalization_attempts:
356
+ assignments.append("finalization_attempts = finalization_attempts + 1")
357
+ if status == AgentRunStatus.AGENT_COMPLETED:
358
+ assignments.append("agent_completed_at = ?")
359
+ params.append(current_timestamp)
360
+ if status in (AgentRunStatus.FINALIZED, AgentRunStatus.FINALIZED_PENDING_TOOL):
361
+ assignments.append("finalized_at = ?")
362
+ params.append(current_timestamp)
363
+ params.append(run_id)
364
+ with self._lock:
365
+ self.conn.execute(
366
+ f"UPDATE _agent_runs SET {', '.join(assignments)} WHERE id = ?",
367
+ params,
368
+ )
369
+ self.conn.commit()
370
+ return self.get_agent_run(run_id)
371
+
372
+ @SQLiteStorageBase.handle_exceptions
373
+ def create_pending_tool_call(
374
+ self, record: PendingToolCallRecord
375
+ ) -> PendingToolCallRecord:
376
+ with self._lock:
377
+ self.conn.execute(
378
+ """
379
+ INSERT INTO _pending_tool_calls (
380
+ id, org_id, user_id, scope, scope_hash, tool_name, dedup_key,
381
+ status, question_text, answer_format, args, tags, result,
382
+ embedding, superseded_by, resolved_at, expires_at, cache_until,
383
+ valid_until
384
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
385
+ """,
386
+ (
387
+ record.id,
388
+ record.org_id,
389
+ record.user_id,
390
+ _json_dumps(record.scope),
391
+ record.scope_hash,
392
+ record.tool_name,
393
+ record.dedup_key,
394
+ record.status.value,
395
+ record.question_text,
396
+ record.answer_format,
397
+ _json_dumps(record.args),
398
+ _json_dumps(record.tags),
399
+ _json_dumps(record.result),
400
+ _json_dumps(record.embedding),
401
+ record.superseded_by,
402
+ _dt_str(record.resolved_at),
403
+ _dt_str(record.expires_at),
404
+ _dt_str(record.cache_until),
405
+ _dt_str(record.valid_until),
406
+ ),
407
+ )
408
+ self.conn.commit()
409
+ stored = self.get_pending_tool_call(record.id)
410
+ if stored is None: # pragma: no cover
411
+ raise RuntimeError(f"Failed to create pending tool call {record.id}")
412
+ return stored
413
+
414
+ def _insert_pending_tool_call_unlocked(self, record: PendingToolCallRecord) -> None:
415
+ self.conn.execute(
416
+ """
417
+ INSERT INTO _pending_tool_calls (
418
+ id, org_id, user_id, scope, scope_hash, tool_name, dedup_key,
419
+ status, question_text, answer_format, args, tags, result,
420
+ embedding, superseded_by, resolved_at, expires_at, cache_until,
421
+ valid_until
422
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
423
+ """,
424
+ (
425
+ record.id,
426
+ record.org_id,
427
+ record.user_id,
428
+ _json_dumps(record.scope),
429
+ record.scope_hash,
430
+ record.tool_name,
431
+ record.dedup_key,
432
+ record.status.value,
433
+ record.question_text,
434
+ record.answer_format,
435
+ _json_dumps(record.args),
436
+ _json_dumps(record.tags),
437
+ _json_dumps(record.result),
438
+ _json_dumps(record.embedding),
439
+ record.superseded_by,
440
+ _dt_str(record.resolved_at),
441
+ _dt_str(record.expires_at),
442
+ _dt_str(record.cache_until),
443
+ _dt_str(record.valid_until),
444
+ ),
445
+ )
446
+
447
+ @SQLiteStorageBase.handle_exceptions
448
+ def create_or_attach_pending_tool_call(
449
+ self,
450
+ *,
451
+ record: PendingToolCallRecord,
452
+ dependency: RunToolDependencyRecord,
453
+ now: datetime | None = None,
454
+ ) -> PendingToolCallUpsertResult:
455
+ current = now or datetime.now(UTC)
456
+ created = False
457
+ pending_tool_call_id = record.id
458
+ with self._lock:
459
+ self.conn.execute("BEGIN IMMEDIATE")
460
+ try:
461
+ row = self.conn.execute(
462
+ """
463
+ SELECT * FROM _pending_tool_calls
464
+ WHERE org_id = ?
465
+ AND scope_hash = ?
466
+ AND tool_name = ?
467
+ AND dedup_key = ?
468
+ AND status = ?
469
+ AND cache_until > ?
470
+ ORDER BY created_at ASC
471
+ LIMIT 1
472
+ """,
473
+ (
474
+ record.org_id,
475
+ record.scope_hash,
476
+ record.tool_name,
477
+ record.dedup_key,
478
+ PendingToolCallStatus.PENDING.value,
479
+ _dt_str(current),
480
+ ),
481
+ ).fetchone()
482
+ pending_tool_call_id = row["id"] if row is not None else record.id
483
+ if row is None:
484
+ self._insert_pending_tool_call_unlocked(record)
485
+ created = True
486
+ self.conn.execute(
487
+ """
488
+ INSERT OR IGNORE INTO _run_tool_dependencies (
489
+ run_id, pending_tool_call_id, dependency_kind,
490
+ resolved_at, consumed_at
491
+ ) VALUES (?,?,?,?,?)
492
+ """,
493
+ (
494
+ dependency.run_id,
495
+ pending_tool_call_id,
496
+ dependency.dependency_kind.value,
497
+ _dt_str(dependency.resolved_at),
498
+ _dt_str(dependency.consumed_at),
499
+ ),
500
+ )
501
+ except Exception:
502
+ self.conn.rollback()
503
+ raise
504
+ else:
505
+ self.conn.commit()
506
+
507
+ stored = self.get_pending_tool_call(pending_tool_call_id)
508
+ if stored is None: # pragma: no cover
509
+ raise RuntimeError("Failed to create or attach pending tool call")
510
+ return PendingToolCallUpsertResult(pending_tool_call=stored, created=created)
511
+
512
+ @SQLiteStorageBase.handle_exceptions
513
+ def get_pending_tool_call(self, call_id: str) -> PendingToolCallRecord | None:
514
+ row = self._fetchone(
515
+ "SELECT * FROM _pending_tool_calls WHERE id = ?", (call_id,)
516
+ )
517
+ return _row_to_pending_tool_call(row) if row else None
518
+
519
+ @SQLiteStorageBase.handle_exceptions
520
+ def list_pending_tool_calls(
521
+ self,
522
+ *,
523
+ status: PendingToolCallStatus | None = None,
524
+ limit: int = 100,
525
+ ) -> list[PendingToolCallRecord]:
526
+ bounded_limit = max(1, min(limit, 500))
527
+ params: list[Any] = [self.org_id]
528
+ status_clause = ""
529
+ if status is not None:
530
+ status_clause = "AND status = ?"
531
+ params.append(status.value)
532
+ params.append(bounded_limit)
533
+ rows = self._fetchall(
534
+ f"""
535
+ SELECT * FROM _pending_tool_calls
536
+ WHERE org_id = ?
537
+ {status_clause}
538
+ ORDER BY created_at DESC, id ASC
539
+ LIMIT ?
540
+ """,
541
+ tuple(params),
542
+ )
543
+ return [_row_to_pending_tool_call(row) for row in rows]
544
+
545
+ @SQLiteStorageBase.handle_exceptions
546
+ def cancel_pending_tool_call(
547
+ self,
548
+ call_id: str,
549
+ *,
550
+ cancelled_at: datetime | None = None,
551
+ ) -> PendingToolCallRecord | None:
552
+ now = cancelled_at or datetime.now(UTC)
553
+ now_s = _dt_str(now)
554
+ with self._lock:
555
+ self.conn.execute(
556
+ """
557
+ UPDATE _pending_tool_calls
558
+ SET status = ?
559
+ WHERE id = ?
560
+ AND status = ?
561
+ """,
562
+ (
563
+ PendingToolCallStatus.CANCELLED.value,
564
+ call_id,
565
+ PendingToolCallStatus.PENDING.value,
566
+ ),
567
+ )
568
+ self.conn.execute(
569
+ """
570
+ UPDATE _run_tool_dependencies
571
+ SET resolved_at = ?
572
+ WHERE pending_tool_call_id = ? AND resolved_at IS NULL
573
+ """,
574
+ (now_s, call_id),
575
+ )
576
+ self._finalize_runs_without_pending_dependencies_unlocked(now_s or "")
577
+ self.conn.commit()
578
+ return self.get_pending_tool_call(call_id)
579
+
580
+ @SQLiteStorageBase.handle_exceptions
581
+ def expire_pending_tool_calls(
582
+ self,
583
+ *,
584
+ now: datetime | None = None,
585
+ limit: int = 100,
586
+ ) -> int:
587
+ current = now or datetime.now(UTC)
588
+ now_s = _dt_str(current)
589
+ bounded_limit = max(1, min(limit, 500))
590
+ with self._lock:
591
+ self.conn.execute("BEGIN IMMEDIATE")
592
+ try:
593
+ rows = self.conn.execute(
594
+ """
595
+ SELECT id
596
+ FROM _pending_tool_calls
597
+ WHERE status = ?
598
+ AND expires_at IS NOT NULL
599
+ AND expires_at <= ?
600
+ ORDER BY expires_at ASC, created_at ASC, id ASC
601
+ LIMIT ?
602
+ """,
603
+ (
604
+ PendingToolCallStatus.PENDING.value,
605
+ now_s,
606
+ bounded_limit,
607
+ ),
608
+ ).fetchall()
609
+ call_ids = [row["id"] for row in rows]
610
+ if not call_ids:
611
+ self.conn.commit()
612
+ return 0
613
+
614
+ placeholders = ",".join("?" for _ in call_ids)
615
+ self.conn.execute(
616
+ f"""
617
+ UPDATE _pending_tool_calls
618
+ SET status = ?
619
+ WHERE id IN ({placeholders})
620
+ AND status = ?
621
+ """,
622
+ (
623
+ PendingToolCallStatus.EXPIRED.value,
624
+ *call_ids,
625
+ PendingToolCallStatus.PENDING.value,
626
+ ),
627
+ )
628
+ self.conn.execute(
629
+ f"""
630
+ UPDATE _run_tool_dependencies
631
+ SET resolved_at = ?
632
+ WHERE pending_tool_call_id IN ({placeholders})
633
+ AND resolved_at IS NULL
634
+ """,
635
+ (now_s, *call_ids),
636
+ )
637
+ self._finalize_runs_without_pending_dependencies_unlocked(now_s or "")
638
+ except Exception:
639
+ self.conn.rollback()
640
+ raise
641
+ else:
642
+ self.conn.commit()
643
+ return len(call_ids)
644
+
645
+ @SQLiteStorageBase.handle_exceptions
646
+ def find_active_pending_tool_call(
647
+ self,
648
+ *,
649
+ org_id: str,
650
+ scope_hash: str,
651
+ tool_name: str,
652
+ dedup_key: str,
653
+ now: datetime | None = None,
654
+ ) -> PendingToolCallRecord | None:
655
+ now_s = _dt_str(now or datetime.now(UTC))
656
+ row = self._fetchone(
657
+ """
658
+ SELECT * FROM _pending_tool_calls
659
+ WHERE org_id = ?
660
+ AND scope_hash = ?
661
+ AND tool_name = ?
662
+ AND dedup_key = ?
663
+ AND status = ?
664
+ AND cache_until > ?
665
+ ORDER BY created_at ASC
666
+ LIMIT 1
667
+ """,
668
+ (
669
+ org_id,
670
+ scope_hash,
671
+ tool_name,
672
+ dedup_key,
673
+ PendingToolCallStatus.PENDING.value,
674
+ now_s,
675
+ ),
676
+ )
677
+ return _row_to_pending_tool_call(row) if row else None
678
+
679
+ @SQLiteStorageBase.handle_exceptions
680
+ def search_prior_tool_calls(
681
+ self,
682
+ *,
683
+ org_id: str,
684
+ scope_hash: str,
685
+ tool_name: str,
686
+ query_embedding: list[float] | None = None,
687
+ now: datetime | None = None,
688
+ limit: int = 8,
689
+ ) -> list[PriorAnswerMatch]:
690
+ current = now or datetime.now(UTC)
691
+ bounded_limit = max(1, min(limit, 50))
692
+ rows = self._fetchall(
693
+ """
694
+ SELECT * FROM _pending_tool_calls
695
+ WHERE org_id = ?
696
+ AND scope_hash = ?
697
+ AND tool_name = ?
698
+ AND (
699
+ (
700
+ status = ?
701
+ AND (expires_at IS NULL OR expires_at > ?)
702
+ )
703
+ OR (
704
+ status = ?
705
+ AND (valid_until IS NULL OR valid_until > ?)
706
+ )
707
+ )
708
+ ORDER BY
709
+ CASE status WHEN ? THEN 0 ELSE 1 END,
710
+ COALESCE(resolved_at, created_at) DESC,
711
+ id ASC
712
+ """,
713
+ (
714
+ org_id,
715
+ scope_hash,
716
+ tool_name,
717
+ PendingToolCallStatus.PENDING.value,
718
+ _dt_str(current),
719
+ PendingToolCallStatus.RESOLVED.value,
720
+ _dt_str(current),
721
+ PendingToolCallStatus.RESOLVED.value,
722
+ ),
723
+ )
724
+ seen_resolved_dedup_keys: set[str] = set()
725
+ records: list[PendingToolCallRecord] = []
726
+ for row in rows:
727
+ record = _row_to_pending_tool_call(row)
728
+ if record.status == PendingToolCallStatus.RESOLVED:
729
+ if record.dedup_key in seen_resolved_dedup_keys:
730
+ continue
731
+ seen_resolved_dedup_keys.add(record.dedup_key)
732
+ records.append(record)
733
+ matches = [
734
+ _record_to_prior_answer_match(record, query_embedding=query_embedding)
735
+ for record in records
736
+ ]
737
+ if query_embedding:
738
+ matches.sort(
739
+ key=lambda match: (
740
+ match.similarity is not None,
741
+ match.similarity or -1.0,
742
+ match.resolved_at
743
+ or match.created_at
744
+ or datetime.min.replace(tzinfo=UTC),
745
+ ),
746
+ reverse=True,
747
+ )
748
+ return matches[:bounded_limit]
749
+
750
+ @SQLiteStorageBase.handle_exceptions
751
+ def attach_run_tool_dependency(
752
+ self, record: RunToolDependencyRecord
753
+ ) -> RunToolDependencyRecord:
754
+ with self._lock:
755
+ self.conn.execute(
756
+ """
757
+ INSERT OR IGNORE INTO _run_tool_dependencies (
758
+ run_id, pending_tool_call_id, dependency_kind, resolved_at,
759
+ consumed_at
760
+ ) VALUES (?,?,?,?,?)
761
+ """,
762
+ (
763
+ record.run_id,
764
+ record.pending_tool_call_id,
765
+ record.dependency_kind.value,
766
+ _dt_str(record.resolved_at),
767
+ _dt_str(record.consumed_at),
768
+ ),
769
+ )
770
+ self.conn.commit()
771
+ row = self._fetchone(
772
+ """
773
+ SELECT * FROM _run_tool_dependencies
774
+ WHERE run_id = ? AND pending_tool_call_id = ?
775
+ """,
776
+ (record.run_id, record.pending_tool_call_id),
777
+ )
778
+ if row is None: # pragma: no cover
779
+ raise RuntimeError("Failed to attach run tool dependency")
780
+ return _row_to_run_tool_dependency(row)
781
+
782
+ @SQLiteStorageBase.handle_exceptions
783
+ def count_unresolved_followup_dependencies(
784
+ self,
785
+ *,
786
+ org_id: str,
787
+ extractor_kind: str,
788
+ tool_name: str,
789
+ ) -> int:
790
+ row = self._fetchone(
791
+ """
792
+ SELECT COUNT(*) AS count
793
+ FROM _run_tool_dependencies d
794
+ JOIN _agent_runs r ON r.id = d.run_id
795
+ JOIN _pending_tool_calls p ON p.id = d.pending_tool_call_id
796
+ WHERE r.org_id = ?
797
+ AND r.extractor_kind = ?
798
+ AND p.tool_name = ?
799
+ AND p.status = ?
800
+ AND d.resolved_at IS NULL
801
+ AND d.consumed_at IS NULL
802
+ """,
803
+ (
804
+ org_id,
805
+ extractor_kind,
806
+ tool_name,
807
+ PendingToolCallStatus.PENDING.value,
808
+ ),
809
+ )
810
+ return int(row["count"]) if row is not None else 0
811
+
812
+ @SQLiteStorageBase.handle_exceptions
813
+ def list_run_tool_dependencies(self, run_id: str) -> list[RunToolDependencyRecord]:
814
+ rows = self._fetchall(
815
+ "SELECT * FROM _run_tool_dependencies WHERE run_id = ?",
816
+ (run_id,),
817
+ )
818
+ return [_row_to_run_tool_dependency(row) for row in rows]
819
+
820
+ @SQLiteStorageBase.handle_exceptions
821
+ def resolve_pending_tool_call(
822
+ self,
823
+ call_id: str,
824
+ *,
825
+ result: dict[str, Any],
826
+ resolved_at: datetime | None = None,
827
+ valid_for_seconds: int,
828
+ ) -> PendingToolCallRecord | None:
829
+ resolved = resolved_at or datetime.now(UTC)
830
+ valid_until = resolved + timedelta(seconds=valid_for_seconds)
831
+ with self._lock:
832
+ cur = self.conn.execute(
833
+ """
834
+ UPDATE _pending_tool_calls
835
+ SET status = ?, result = ?, resolved_at = ?, valid_until = ?
836
+ WHERE id = ?
837
+ AND status = ?
838
+ """,
839
+ (
840
+ PendingToolCallStatus.RESOLVED.value,
841
+ _json_dumps(result),
842
+ _dt_str(resolved),
843
+ _dt_str(valid_until),
844
+ call_id,
845
+ PendingToolCallStatus.PENDING.value,
846
+ ),
847
+ )
848
+ if cur.rowcount == 0:
849
+ self.conn.commit()
850
+ return self.get_pending_tool_call(call_id)
851
+ self.conn.execute(
852
+ """
853
+ UPDATE _pending_tool_calls
854
+ SET status = ?,
855
+ superseded_by = ?
856
+ WHERE id != ?
857
+ AND status = ?
858
+ AND (valid_until IS NULL OR valid_until > ?)
859
+ AND (org_id, scope_hash, tool_name, dedup_key) = (
860
+ SELECT org_id, scope_hash, tool_name, dedup_key
861
+ FROM _pending_tool_calls
862
+ WHERE id = ?
863
+ )
864
+ """,
865
+ (
866
+ PendingToolCallStatus.SUPERSEDED.value,
867
+ call_id,
868
+ call_id,
869
+ PendingToolCallStatus.RESOLVED.value,
870
+ _dt_str(resolved),
871
+ call_id,
872
+ ),
873
+ )
874
+ self.conn.execute(
875
+ """
876
+ UPDATE _run_tool_dependencies
877
+ SET resolved_at = ?
878
+ WHERE pending_tool_call_id = ? AND resolved_at IS NULL
879
+ """,
880
+ (_dt_str(resolved), call_id),
881
+ )
882
+ self.conn.execute(
883
+ """
884
+ UPDATE _agent_runs
885
+ SET status = ?, updated_at = ?
886
+ WHERE status IN (?, ?)
887
+ AND EXISTS (
888
+ SELECT 1
889
+ FROM _run_tool_dependencies d
890
+ WHERE d.run_id = _agent_runs.id
891
+ AND d.pending_tool_call_id = ?
892
+ AND d.resolved_at IS NOT NULL
893
+ AND d.consumed_at IS NULL
894
+ )
895
+ """,
896
+ (
897
+ AgentRunStatus.RESUME_READY.value,
898
+ self._current_timestamp(),
899
+ AgentRunStatus.FINALIZED.value,
900
+ AgentRunStatus.FINALIZED_PENDING_TOOL.value,
901
+ call_id,
902
+ ),
903
+ )
904
+ self.conn.commit()
905
+ return self.get_pending_tool_call(call_id)
906
+
907
+ @SQLiteStorageBase.handle_exceptions
908
+ def update_resolved_pending_tool_call_result(
909
+ self,
910
+ call_id: str,
911
+ *,
912
+ result: dict[str, Any],
913
+ resolved_at: datetime | None = None,
914
+ valid_for_seconds: int,
915
+ ) -> PendingToolCallRecord | None:
916
+ resolved = resolved_at or datetime.now(UTC)
917
+ valid_until = resolved + timedelta(seconds=valid_for_seconds)
918
+ now_s = _dt_str(resolved) or self._current_timestamp()
919
+ with self._lock:
920
+ self.conn.execute("BEGIN IMMEDIATE")
921
+ try:
922
+ cur = self.conn.execute(
923
+ """
924
+ UPDATE _pending_tool_calls
925
+ SET result = ?, resolved_at = ?, valid_until = ?
926
+ WHERE id = ?
927
+ AND status = ?
928
+ """,
929
+ (
930
+ _json_dumps(result),
931
+ _dt_str(resolved),
932
+ _dt_str(valid_until),
933
+ call_id,
934
+ PendingToolCallStatus.RESOLVED.value,
935
+ ),
936
+ )
937
+ if cur.rowcount == 0:
938
+ self.conn.commit()
939
+ return self.get_pending_tool_call(call_id)
940
+ self.conn.execute(
941
+ """
942
+ UPDATE _pending_tool_calls
943
+ SET status = ?,
944
+ superseded_by = ?
945
+ WHERE id != ?
946
+ AND status = ?
947
+ AND (valid_until IS NULL OR valid_until > ?)
948
+ AND (org_id, scope_hash, tool_name, dedup_key) = (
949
+ SELECT org_id, scope_hash, tool_name, dedup_key
950
+ FROM _pending_tool_calls
951
+ WHERE id = ?
952
+ )
953
+ """,
954
+ (
955
+ PendingToolCallStatus.SUPERSEDED.value,
956
+ call_id,
957
+ call_id,
958
+ PendingToolCallStatus.RESOLVED.value,
959
+ _dt_str(resolved),
960
+ call_id,
961
+ ),
962
+ )
963
+ self.conn.execute(
964
+ """
965
+ UPDATE _run_tool_dependencies
966
+ SET resolved_at = ?,
967
+ consumed_at = NULL
968
+ WHERE pending_tool_call_id = ?
969
+ """,
970
+ (_dt_str(resolved), call_id),
971
+ )
972
+ self._mark_runs_ready_with_actionable_dependencies_unlocked(
973
+ now_s, pending_tool_call_id=call_id
974
+ )
975
+ except Exception:
976
+ self.conn.rollback()
977
+ raise
978
+ else:
979
+ self.conn.commit()
980
+ return self.get_pending_tool_call(call_id)
981
+
982
+ @SQLiteStorageBase.handle_exceptions
983
+ def mark_pending_tool_call_not_applicable(
984
+ self,
985
+ call_id: str,
986
+ *,
987
+ resolved_at: datetime | None = None,
988
+ valid_for_seconds: int,
989
+ ) -> PendingToolCallRecord | None:
990
+ resolved = resolved_at or datetime.now(UTC)
991
+ valid_until = resolved + timedelta(seconds=valid_for_seconds)
992
+ now_s = _dt_str(resolved) or self._current_timestamp()
993
+ with self._lock:
994
+ self.conn.execute("BEGIN IMMEDIATE")
995
+ try:
996
+ cur = self.conn.execute(
997
+ """
998
+ UPDATE _pending_tool_calls
999
+ SET status = ?, result = ?, resolved_at = ?, valid_until = ?
1000
+ WHERE id = ?
1001
+ AND status IN (?, ?)
1002
+ """,
1003
+ (
1004
+ PendingToolCallStatus.RESOLVED.value,
1005
+ _json_dumps(not_applicable_tool_result()),
1006
+ _dt_str(resolved),
1007
+ _dt_str(valid_until),
1008
+ call_id,
1009
+ PendingToolCallStatus.PENDING.value,
1010
+ PendingToolCallStatus.RESOLVED.value,
1011
+ ),
1012
+ )
1013
+ if cur.rowcount == 0:
1014
+ self.conn.commit()
1015
+ return self.get_pending_tool_call(call_id)
1016
+ self.conn.execute(
1017
+ """
1018
+ UPDATE _run_tool_dependencies
1019
+ SET resolved_at = COALESCE(resolved_at, ?),
1020
+ consumed_at = ?
1021
+ WHERE pending_tool_call_id = ?
1022
+ AND consumed_at IS NULL
1023
+ """,
1024
+ (_dt_str(resolved), _dt_str(resolved), call_id),
1025
+ )
1026
+ self._mark_runs_ready_with_actionable_dependencies_unlocked(
1027
+ now_s, pending_tool_call_id=call_id
1028
+ )
1029
+ self._finalize_runs_without_actionable_dependencies_unlocked(
1030
+ now_s, pending_tool_call_id=call_id
1031
+ )
1032
+ except Exception:
1033
+ self.conn.rollback()
1034
+ raise
1035
+ else:
1036
+ self.conn.commit()
1037
+ return self.get_pending_tool_call(call_id)
1038
+
1039
+ @SQLiteStorageBase.handle_exceptions
1040
+ def claim_ready_agent_run(
1041
+ self,
1042
+ *,
1043
+ org_id: str,
1044
+ worker_id: str,
1045
+ now: datetime | None = None,
1046
+ claim_ttl_seconds: int = 600,
1047
+ ) -> AgentRunRecord | None:
1048
+ current = now or datetime.now(UTC)
1049
+ stale_before = current - timedelta(seconds=claim_ttl_seconds)
1050
+ with self._lock:
1051
+ row = self.conn.execute(
1052
+ """
1053
+ SELECT r.*
1054
+ FROM _agent_runs r
1055
+ WHERE r.org_id = ?
1056
+ AND (
1057
+ r.status = ?
1058
+ OR (r.status = ? AND r.claimed_at < ?)
1059
+ )
1060
+ AND (r.next_resume_at IS NULL OR r.next_resume_at <= ?)
1061
+ AND EXISTS (
1062
+ SELECT 1
1063
+ FROM _run_tool_dependencies d
1064
+ JOIN _pending_tool_calls p
1065
+ ON p.id = d.pending_tool_call_id
1066
+ WHERE d.run_id = r.id
1067
+ AND d.resolved_at IS NOT NULL
1068
+ AND d.consumed_at IS NULL
1069
+ AND p.status = ?
1070
+ AND COALESCE(json_extract(p.result, '$.not_applicable'), 0) != 1
1071
+ )
1072
+ ORDER BY
1073
+ r.org_id ASC,
1074
+ r.extractor_kind ASC,
1075
+ COALESCE(r.user_id, '') ASC,
1076
+ COALESCE(r.window_start_interaction_id, 0) ASC,
1077
+ r.updated_at ASC
1078
+ LIMIT 1
1079
+ """,
1080
+ (
1081
+ org_id,
1082
+ AgentRunStatus.RESUME_READY.value,
1083
+ AgentRunStatus.RESUMING.value,
1084
+ _dt_str(stale_before),
1085
+ _dt_str(current),
1086
+ PendingToolCallStatus.RESOLVED.value,
1087
+ ),
1088
+ ).fetchone()
1089
+ if row is None:
1090
+ return None
1091
+ self.conn.execute(
1092
+ """
1093
+ UPDATE _agent_runs
1094
+ SET status = ?,
1095
+ claimed_by = ?,
1096
+ claimed_at = ?,
1097
+ resume_attempts = resume_attempts + 1,
1098
+ updated_at = ?
1099
+ WHERE id = ?
1100
+ """,
1101
+ (
1102
+ AgentRunStatus.RESUMING.value,
1103
+ worker_id,
1104
+ _dt_str(current),
1105
+ self._current_timestamp(),
1106
+ row["id"],
1107
+ ),
1108
+ )
1109
+ self.conn.commit()
1110
+ return self.get_agent_run(row["id"])
1111
+
1112
+ @SQLiteStorageBase.handle_exceptions
1113
+ def claim_finalization_failed_agent_run(
1114
+ self,
1115
+ *,
1116
+ org_id: str,
1117
+ worker_id: str,
1118
+ now: datetime | None = None,
1119
+ claim_ttl_seconds: int = 600,
1120
+ ) -> AgentRunRecord | None:
1121
+ current = now or datetime.now(UTC)
1122
+ stale_before = current - timedelta(seconds=claim_ttl_seconds)
1123
+ with self._lock:
1124
+ # Claim runs that still need their committed output finalized:
1125
+ # - FINALIZATION_FAILED: an explicit retry is due.
1126
+ # - stale FINALIZING: a worker crashed mid-finalize.
1127
+ # - stale AGENT_COMPLETED: publish-time finalization never ran or
1128
+ # crashed before flipping the status (the run row carries
1129
+ # committed_output but was orphaned); the staleness guard ensures
1130
+ # we never race an in-flight publish-time finalize.
1131
+ row = self.conn.execute(
1132
+ """
1133
+ SELECT *
1134
+ FROM _agent_runs
1135
+ WHERE org_id = ?
1136
+ AND (
1137
+ status = ?
1138
+ OR (status = ? AND claimed_at < ?)
1139
+ OR (status = ? AND updated_at < ?)
1140
+ )
1141
+ AND committed_output IS NOT NULL
1142
+ AND (next_resume_at IS NULL OR next_resume_at <= ?)
1143
+ ORDER BY
1144
+ org_id ASC,
1145
+ extractor_kind ASC,
1146
+ COALESCE(user_id, '') ASC,
1147
+ COALESCE(window_start_interaction_id, 0) ASC,
1148
+ updated_at ASC
1149
+ LIMIT 1
1150
+ """,
1151
+ (
1152
+ org_id,
1153
+ AgentRunStatus.FINALIZATION_FAILED.value,
1154
+ AgentRunStatus.FINALIZING.value,
1155
+ _dt_str(stale_before),
1156
+ AgentRunStatus.AGENT_COMPLETED.value,
1157
+ _dt_str(stale_before),
1158
+ _dt_str(current),
1159
+ ),
1160
+ ).fetchone()
1161
+ if row is None:
1162
+ return None
1163
+ self.conn.execute(
1164
+ """
1165
+ UPDATE _agent_runs
1166
+ SET status = ?,
1167
+ claimed_by = ?,
1168
+ claimed_at = ?,
1169
+ updated_at = ?
1170
+ WHERE id = ?
1171
+ """,
1172
+ (
1173
+ AgentRunStatus.FINALIZING.value,
1174
+ worker_id,
1175
+ _dt_str(current),
1176
+ self._current_timestamp(),
1177
+ row["id"],
1178
+ ),
1179
+ )
1180
+ self.conn.commit()
1181
+ return self.get_agent_run(row["id"])
1182
+
1183
+ @SQLiteStorageBase.handle_exceptions
1184
+ def consume_run_tool_dependencies(self, run_id: str) -> int:
1185
+ consumed_at = self._current_timestamp()
1186
+ with self._lock:
1187
+ cur = self.conn.execute(
1188
+ """
1189
+ UPDATE _run_tool_dependencies
1190
+ SET consumed_at = ?
1191
+ WHERE run_id = ?
1192
+ AND resolved_at IS NOT NULL
1193
+ AND consumed_at IS NULL
1194
+ """,
1195
+ (consumed_at, run_id),
1196
+ )
1197
+ self.conn.commit()
1198
+ return int(cur.rowcount)
1199
+
1200
+ @SQLiteStorageBase.handle_exceptions
1201
+ def list_resumable_work_org_ids(
1202
+ self,
1203
+ *,
1204
+ now: datetime | None = None,
1205
+ limit: int = 1000,
1206
+ ) -> list[str]:
1207
+ current = now or datetime.now(UTC)
1208
+ now_s = _dt_str(current)
1209
+ bounded_limit = max(1, min(limit, 10_000))
1210
+ # Cross-org maintenance query. Surfaces any org that has a run ready to
1211
+ # resume / awaiting finalization, or a pending tool call due to expire.
1212
+ rows = self._fetchall(
1213
+ """
1214
+ SELECT DISTINCT org_id FROM (
1215
+ SELECT r.org_id
1216
+ FROM _agent_runs r
1217
+ WHERE r.status IN (?, ?)
1218
+ AND EXISTS (
1219
+ SELECT 1
1220
+ FROM _run_tool_dependencies d
1221
+ JOIN _pending_tool_calls p
1222
+ ON p.id = d.pending_tool_call_id
1223
+ WHERE d.run_id = r.id
1224
+ AND d.resolved_at IS NOT NULL
1225
+ AND d.consumed_at IS NULL
1226
+ AND p.status = ?
1227
+ AND COALESCE(json_extract(p.result, '$.not_applicable'), 0) != 1
1228
+ )
1229
+ UNION
1230
+ SELECT org_id FROM _agent_runs
1231
+ WHERE status IN (?, ?, ?)
1232
+ UNION
1233
+ SELECT org_id FROM _pending_tool_calls
1234
+ WHERE status = ?
1235
+ AND expires_at IS NOT NULL
1236
+ AND expires_at <= ?
1237
+ )
1238
+ ORDER BY org_id ASC
1239
+ LIMIT ?
1240
+ """,
1241
+ (
1242
+ AgentRunStatus.RESUME_READY.value,
1243
+ AgentRunStatus.RESUMING.value,
1244
+ PendingToolCallStatus.RESOLVED.value,
1245
+ AgentRunStatus.FINALIZATION_FAILED.value,
1246
+ AgentRunStatus.FINALIZING.value,
1247
+ AgentRunStatus.AGENT_COMPLETED.value,
1248
+ PendingToolCallStatus.PENDING.value,
1249
+ now_s,
1250
+ bounded_limit,
1251
+ ),
1252
+ )
1253
+ return [row["org_id"] for row in rows]