claude-smart 0.2.45 → 0.2.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +34 -5
  3. package/bin/claude-smart.js +295 -5
  4. package/package.json +22 -3
  5. package/plugin/.claude-plugin/plugin.json +4 -2
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +22 -2
  8. package/plugin/commands/clear-all.md +1 -0
  9. package/plugin/commands/dashboard.md +1 -0
  10. package/plugin/commands/learn.md +1 -0
  11. package/plugin/commands/restart.md +1 -0
  12. package/plugin/commands/show.md +1 -0
  13. package/plugin/dashboard/app/configure/env/page.tsx +40 -14
  14. package/plugin/dashboard/app/configure/server/page.tsx +51 -1
  15. package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
  16. package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
  17. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
  18. package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
  19. package/plugin/dashboard/lib/reflexio-client.ts +16 -0
  20. package/plugin/dashboard/lib/status.ts +10 -3
  21. package/plugin/dashboard/lib/types.ts +18 -6
  22. package/plugin/hooks/codex-hooks.json +0 -1
  23. package/plugin/opencode/assistant-buffer.ts +108 -0
  24. package/plugin/opencode/dist/assistant-buffer.js +96 -0
  25. package/plugin/opencode/dist/internal.js +12 -0
  26. package/plugin/opencode/dist/payload.js +85 -0
  27. package/plugin/opencode/dist/server.mjs +158 -0
  28. package/plugin/opencode/internal.ts +18 -0
  29. package/plugin/opencode/package.json +3 -0
  30. package/plugin/opencode/payload.ts +90 -0
  31. package/plugin/opencode/server.mts +174 -0
  32. package/plugin/opencode/tsconfig.json +13 -0
  33. package/plugin/pyproject.toml +20 -3
  34. package/plugin/scripts/_lib.sh +5 -1
  35. package/plugin/scripts/backend-service.sh +22 -2
  36. package/plugin/scripts/ensure-plugin-root.sh +4 -4
  37. package/plugin/scripts/hook_entry.sh +1 -1
  38. package/plugin/scripts/opencode-claude-compat +4 -0
  39. package/plugin/scripts/opencode-claude-compat.cmd +3 -0
  40. package/plugin/scripts/opencode-claude-compat.js +225 -0
  41. package/plugin/scripts/smart-install.sh +10 -8
  42. package/plugin/src/README.md +1 -1
  43. package/plugin/src/claude_smart/cli.py +304 -6
  44. package/plugin/src/claude_smart/env_config.py +12 -4
  45. package/plugin/src/claude_smart/events/session_start.py +26 -7
  46. package/plugin/src/claude_smart/events/stop.py +1 -1
  47. package/plugin/src/claude_smart/ids.py +1 -1
  48. package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
  49. package/plugin/src/claude_smart/runtime.py +7 -1
  50. package/plugin/uv.lock +1 -1
  51. package/plugin/vendor/reflexio/.env.example +54 -30
  52. package/plugin/vendor/reflexio/README.md +14 -8
  53. package/plugin/vendor/reflexio/pyproject.toml +13 -1
  54. package/plugin/vendor/reflexio/reflexio/README.md +1 -0
  55. package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
  56. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
  57. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
  58. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
  59. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
  60. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +210 -50
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
  63. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
  64. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
  65. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
  66. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
  67. package/plugin/vendor/reflexio/reflexio/client/client.py +57 -13
  68. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
  69. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
  70. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
  71. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
  72. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
  141. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +238 -10
  142. package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
  143. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +16 -5
  144. package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
  145. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +198 -6
  146. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
  147. package/plugin/vendor/reflexio/reflexio/lib/_search.py +29 -20
  148. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +2 -1
  149. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
  150. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
  151. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
  152. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
  153. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
  154. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +52 -0
  155. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
  156. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
  157. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +88 -7
  158. package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
  159. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
  160. package/plugin/vendor/reflexio/reflexio/server/README.md +45 -32
  161. package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
  162. package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
  163. package/plugin/vendor/reflexio/reflexio/server/_auth.py +65 -2
  164. package/plugin/vendor/reflexio/reflexio/server/api.py +734 -85
  165. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
  166. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +3 -1
  167. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
  168. package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
  169. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
  170. package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
  171. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
  172. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
  173. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
  174. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
  175. package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
  176. package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
  177. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
  178. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
  179. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
  180. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
  181. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
  182. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
  183. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
  184. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
  185. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
  186. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
  187. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +266 -0
  188. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
  189. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
  190. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
  191. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
  192. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
  193. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
  194. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
  195. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
  196. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
  197. package/plugin/vendor/reflexio/reflexio/server/services/README.md +26 -11
  198. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
  199. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
  200. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
  201. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
  202. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
  203. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
  204. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
  205. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
  206. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
  207. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
  208. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
  209. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
  210. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
  211. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
  212. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
  213. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
  214. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
  215. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
  216. package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
  217. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
  218. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
  219. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
  220. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +39 -16
  221. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
  222. package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
  223. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +193 -0
  224. package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
  225. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
  226. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +11 -12
  227. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
  228. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +316 -178
  229. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
  230. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
  231. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
  232. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
  233. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
  234. package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +84 -0
  235. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
  236. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
  237. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
  238. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
  239. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
  240. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
  241. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +30 -22
  242. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
  243. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
  244. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
  245. package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
  246. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
  247. package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
  248. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +83 -6
  249. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
  250. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
  251. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
  252. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +27 -0
  253. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
  254. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
  255. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
  256. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +642 -197
  257. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +61 -104
  258. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +628 -0
  259. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
  260. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +978 -184
  261. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +519 -128
  262. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +77 -3
  263. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
  264. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
  265. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +131 -20
  266. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
  267. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
  268. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +36 -53
  269. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
  270. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
  271. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +243 -7
  272. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +121 -1
  273. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +60 -0
  274. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
  275. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
  277. package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
  278. package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
  279. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
  280. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +229 -32
  281. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
  282. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
  283. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
  284. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
  285. package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
  286. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
  287. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
  288. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
  289. package/scripts/setup-claude-smart.sh +8 -3
  290. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
  291. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
  292. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
  293. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
  294. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
  295. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
  296. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
  297. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
  298. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
  299. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
  300. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
  301. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
  302. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
  303. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
  304. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
  305. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
  306. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
  307. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
  308. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
  309. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
  310. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
  311. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
  312. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
  313. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
  314. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
  315. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
  316. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
  317. /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
  318. /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
  319. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
  320. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
  321. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
  322. /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
@@ -1,192 +0,0 @@
1
- """Pure aggregation helpers for F2's session-level A/B groups.
2
-
3
- These functions are deliberately DB-free and LLM-free so they can be unit
4
- tested in milliseconds and exercised by mutmut. The storage layer hands
5
- in raw tuples; we hand back SuccessRateTrendByGroup.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import math
11
- from collections import defaultdict
12
- from collections.abc import Iterable
13
- from enum import StrEnum
14
- from typing import Any
15
-
16
- from reflexio.models.api_schema.eval_overview_schema import (
17
- SuccessRateTrendByGroup,
18
- TrendPoint,
19
- )
20
-
21
- # Z-value for a 95% Wald CI on a binomial proportion (two-sided).
22
- _Z_95 = 1.96
23
-
24
- # Cap on the lift CI half-width. With very small sample sizes the Wald CI
25
- # computes to absurd widths (e.g., ±70pp), and rendering that on a
26
- # dashboard implies false precision. 0.5 (= ±50pp) is the product-meaningful
27
- # threshold: any wider than that, the user should think "we don't know"
28
- # and the frontend should consider rendering a low-confidence indicator.
29
- _MAX_CI_HALF_WIDTH = 0.5
30
-
31
-
32
- class GroupAssignment(StrEnum):
33
- """Group label derived from `Request.metadata.reflexio_retrieval_enabled`."""
34
-
35
- TREATMENT = "treatment"
36
- CONTROL = "control"
37
- UNTAGGED = "untagged"
38
-
39
-
40
- def assign_group_from_metadata(metadata: dict[str, Any]) -> GroupAssignment:
41
- """
42
- Bucket a session by its first request's metadata.
43
-
44
- Reads ``metadata.reflexio_retrieval_enabled`` as a true bool. Anything
45
- else (missing, string, int, None) maps to UNTAGGED — we never silently
46
- coerce non-bool values, so customers see how many of their sessions
47
- are tagged inconsistently.
48
-
49
- Args:
50
- metadata (dict[str, Any]): The first request's metadata dict. Non-dict
51
- input is tolerated and routed to UNTAGGED (defensive — the caller
52
- may pass a value read from a JSON column with no type guarantee).
53
-
54
- Returns:
55
- GroupAssignment: TREATMENT, CONTROL, or UNTAGGED.
56
- """
57
- if not isinstance(metadata, dict):
58
- return GroupAssignment.UNTAGGED
59
- value = metadata.get("reflexio_retrieval_enabled")
60
- if value is True:
61
- return GroupAssignment.TREATMENT
62
- if value is False:
63
- return GroupAssignment.CONTROL
64
- return GroupAssignment.UNTAGGED
65
-
66
-
67
- def group_session_outcomes_by_metadata(
68
- outcomes: Iterable[tuple[str, int, bool, dict[str, Any]]],
69
- ) -> dict[GroupAssignment, list[tuple[str, int, bool]]]:
70
- """
71
- Split (session_id, ts, is_success, metadata) tuples into three groups.
72
-
73
- Args:
74
- outcomes (Iterable[tuple[str, int, bool, dict[str, Any]]]): Tuples of
75
- (session_id, ts, is_success, first_request_metadata).
76
-
77
- Returns:
78
- dict[GroupAssignment, list[tuple[str, int, bool]]]: Dict keyed by every
79
- GroupAssignment member (TREATMENT, CONTROL, UNTAGGED) — always present,
80
- possibly empty list — with values being (session_id, ts, is_success)
81
- tuples (metadata dropped after binning).
82
- """
83
- out: dict[GroupAssignment, list[tuple[str, int, bool]]] = {
84
- GroupAssignment.TREATMENT: [],
85
- GroupAssignment.CONTROL: [],
86
- GroupAssignment.UNTAGGED: [],
87
- }
88
- for session_id, ts, is_success, metadata in outcomes:
89
- group = assign_group_from_metadata(metadata)
90
- out[group].append((session_id, ts, is_success))
91
- return out
92
-
93
-
94
- def bucket_outcomes(
95
- outcomes: Iterable[tuple[str, int, bool]],
96
- bucket_seconds: int,
97
- ) -> list[dict[str, int]]:
98
- """
99
- Bucket outcomes by time, returning one dict per bucket.
100
-
101
- Args:
102
- outcomes (Iterable[tuple[str, int, bool]]): Tuples of (session_id, ts,
103
- is_success). ``session_id`` is unused at this stage; kept for future
104
- per-session breakdowns.
105
- bucket_seconds (int): Bucket width in seconds (e.g., 86400 for daily,
106
- 604800 for weekly). Must be positive.
107
-
108
- Returns:
109
- list[dict[str, int]]: List of ``{"ts": bucket_start, "n": int,
110
- "successes": int}``, sorted by ``ts`` ascending. Empty buckets are
111
- omitted.
112
-
113
- Raises:
114
- ValueError: When ``bucket_seconds`` is not positive.
115
- """
116
- if bucket_seconds <= 0:
117
- raise ValueError(f"bucket_seconds must be positive, got {bucket_seconds}")
118
- counts: dict[int, dict[str, int]] = defaultdict(lambda: {"n": 0, "successes": 0})
119
- for _, ts, is_success in outcomes:
120
- bucket_start = (ts // bucket_seconds) * bucket_seconds
121
- counts[bucket_start]["n"] += 1
122
- if is_success:
123
- counts[bucket_start]["successes"] += 1
124
- return [
125
- {"ts": bucket_start, "n": d["n"], "successes": d["successes"]}
126
- for bucket_start, d in sorted(counts.items())
127
- ]
128
-
129
-
130
- def compute_trend_by_group(
131
- outcomes: Iterable[tuple[str, int, bool, dict[str, Any]]],
132
- bucket_seconds: int,
133
- ) -> SuccessRateTrendByGroup:
134
- """
135
- Build the dual+untagged-curve trend payload from raw session outcomes.
136
-
137
- Args:
138
- outcomes (Iterable[tuple[str, int, bool, dict[str, Any]]]): Tuples of
139
- (session_id, ts, is_success, first_request_metadata).
140
- bucket_seconds (int): Bucket width passed to :func:`bucket_outcomes`.
141
-
142
- Returns:
143
- SuccessRateTrendByGroup: Three curves (any of which may be empty).
144
- """
145
- grouped = group_session_outcomes_by_metadata(outcomes)
146
- return SuccessRateTrendByGroup(
147
- treatment=_to_trend_points(grouped[GroupAssignment.TREATMENT], bucket_seconds),
148
- control=_to_trend_points(grouped[GroupAssignment.CONTROL], bucket_seconds),
149
- untagged=_to_trend_points(grouped[GroupAssignment.UNTAGGED], bucket_seconds),
150
- )
151
-
152
-
153
- def _to_trend_points(
154
- outcomes: list[tuple[str, int, bool]],
155
- bucket_seconds: int,
156
- ) -> list[TrendPoint]:
157
- """Convert raw outcomes into bucketed TrendPoints; omit empty buckets."""
158
- return [
159
- TrendPoint(ts=b["ts"], rate=b["successes"] / b["n"], n=b["n"])
160
- for b in bucket_outcomes(outcomes, bucket_seconds)
161
- if b["n"] > 0
162
- ]
163
-
164
-
165
- def compute_lift_with_ci(
166
- n_t: int, p_t: float, n_c: int, p_c: float
167
- ) -> tuple[float | None, float | None]:
168
- """
169
- Compute success-rate lift (treatment − control) and a 95% CI half-width.
170
-
171
- Uses the Wald approximation for the difference of two binomial
172
- proportions. Returns ``(None, None)`` if either group has zero samples —
173
- surfaces honest "no estimate" rather than fake zeros.
174
-
175
- Args:
176
- n_t (int): Treatment group size.
177
- p_t (float): Treatment success rate, in ``[0.0, 1.0]``.
178
- n_c (int): Control group size.
179
- p_c (float): Control success rate, in ``[0.0, 1.0]``.
180
-
181
- Returns:
182
- tuple[float | None, float | None]: ``(lift, ci_half_width)`` — both in
183
- proportion units (e.g., 0.14 for 14pp), or ``(None, None)`` when there
184
- isn't enough data.
185
- """
186
- if n_t <= 0 or n_c <= 0:
187
- return None, None
188
- lift = p_t - p_c
189
- var = (p_t * (1 - p_t)) / n_t + (p_c * (1 - p_c)) / n_c
190
- ci_half = _Z_95 * math.sqrt(var)
191
- ci_half = min(ci_half, _MAX_CI_HALF_WIDTH)
192
- return lift, ci_half
@@ -1,303 +0,0 @@
1
- """Plan-level invariants for the agentic-v2 extraction pipeline.
2
-
3
- Invariants are pure functions over ``ExtractionCtx``. Hard violations drop
4
- offending ops from the commit; soft violations are logged and applied.
5
- See spec §6 for the full catalog and severity policy.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import logging
11
-
12
- from reflexio.server.services.extraction.plan import (
13
- CommitResult,
14
- CreateUserPlaybookOp,
15
- CreateUserProfileOp,
16
- DeleteUserPlaybookOp,
17
- DeleteUserProfileOp,
18
- ExtractionCtx,
19
- Violation,
20
- )
21
-
22
- logger = logging.getLogger(__name__)
23
-
24
- PLAN_SIZE_CAP = 30
25
-
26
-
27
- # --- Hard invariants ---
28
-
29
-
30
- def inv_A_search_before_create(ctx: ExtractionCtx) -> list[Violation]: # noqa: N802
31
- """Every CreateOp must be preceded by ≥1 search_* call this run."""
32
- create_indices = [
33
- i
34
- for i, op in enumerate(ctx.plan)
35
- if isinstance(op, (CreateUserProfileOp, CreateUserPlaybookOp))
36
- ]
37
- if create_indices and ctx.search_count == 0:
38
- return [
39
- Violation(
40
- code="A",
41
- severity="hard",
42
- affected_op_indices=create_indices,
43
- msg="Plan has create ops but no search was performed this run",
44
- )
45
- ]
46
- return []
47
-
48
-
49
- def inv_B_delete_known_id(ctx: ExtractionCtx) -> list[Violation]: # noqa: N802
50
- """Every DeleteOp(id) must reference an id in ctx.known_ids.
51
-
52
- known_ids is populated by search/get/create tool handlers — so deletes
53
- targeting hallucinated ids (agent never saw them) are rejected.
54
- """
55
- violations: list[Violation] = []
56
- for i, op in enumerate(ctx.plan):
57
- if (
58
- isinstance(op, (DeleteUserProfileOp, DeleteUserPlaybookOp))
59
- and op.id not in ctx.known_ids
60
- ):
61
- violations.append(
62
- Violation(
63
- code="B",
64
- severity="hard",
65
- affected_op_indices=[i],
66
- msg=f"Delete of unknown id {op.id!r}",
67
- )
68
- )
69
- return violations
70
-
71
-
72
- def inv_D_plan_size_cap(ctx: ExtractionCtx) -> list[Violation]: # noqa: N802
73
- """Plan cannot exceed PLAN_SIZE_CAP ops — guards runaway loops."""
74
- if len(ctx.plan) > PLAN_SIZE_CAP:
75
- overflow = list(range(PLAN_SIZE_CAP, len(ctx.plan)))
76
- return [
77
- Violation(
78
- code="D",
79
- severity="hard",
80
- affected_op_indices=overflow,
81
- msg=f"Plan size {len(ctx.plan)} exceeds cap {PLAN_SIZE_CAP}",
82
- )
83
- ]
84
- return []
85
-
86
-
87
- def inv_F_no_duplicate_deletes(ctx: ExtractionCtx) -> list[Violation]: # noqa: N802
88
- """Same id cannot be deleted twice in one plan."""
89
- seen: set[str] = set()
90
- violations: list[Violation] = []
91
- for i, op in enumerate(ctx.plan):
92
- if isinstance(op, (DeleteUserProfileOp, DeleteUserPlaybookOp)):
93
- if op.id in seen:
94
- violations.append(
95
- Violation(
96
- code="F",
97
- severity="hard",
98
- affected_op_indices=[i],
99
- msg=f"Duplicate delete of id {op.id!r}",
100
- )
101
- )
102
- else:
103
- seen.add(op.id)
104
- return violations
105
-
106
-
107
- def inv_J_scope_match(_ctx: ExtractionCtx) -> list[Violation]: # noqa: N802
108
- """User_id scope is primarily enforced at the storage layer (handlers inject
109
- ctx.user_id). This invariant is a placeholder for future cross-user checks;
110
- for v1 it is a no-op."""
111
- return []
112
-
113
-
114
- HARD_INVARIANTS = (
115
- inv_A_search_before_create,
116
- inv_B_delete_known_id,
117
- inv_D_plan_size_cap,
118
- inv_F_no_duplicate_deletes,
119
- inv_J_scope_match,
120
- )
121
-
122
-
123
- # --- Soft invariants ---
124
-
125
-
126
- def inv_E_no_duplicate_creates(ctx: ExtractionCtx) -> list[Violation]: # noqa: N802
127
- """Two CreateOps with identical content in one plan = oscillation smell."""
128
- seen: dict[str, int] = {}
129
- violations: list[Violation] = []
130
- for i, op in enumerate(ctx.plan):
131
- key = None
132
- if isinstance(op, CreateUserProfileOp):
133
- key = f"profile::{op.content}"
134
- elif isinstance(op, CreateUserPlaybookOp):
135
- key = f"playbook::{op.trigger}::{op.content}"
136
- if key is None:
137
- continue
138
- if key in seen:
139
- violations.append(
140
- Violation(
141
- code="E",
142
- severity="soft",
143
- affected_op_indices=[i],
144
- msg=f"Duplicate create content at op {i}",
145
- )
146
- )
147
- else:
148
- seen[key] = i
149
- return violations
150
-
151
-
152
- def inv_H_source_span_present(ctx: ExtractionCtx) -> list[Violation]: # noqa: N802
153
- """CreateOps must have non-whitespace source_span.
154
-
155
- Schema enforces min_length=1, but whitespace-only slips through —
156
- this is the secondary guard.
157
- """
158
- violations: list[Violation] = []
159
- for i, op in enumerate(ctx.plan):
160
- if (
161
- isinstance(op, (CreateUserProfileOp, CreateUserPlaybookOp))
162
- and not op.source_span.strip()
163
- ):
164
- violations.append(
165
- Violation(
166
- code="H",
167
- severity="soft",
168
- affected_op_indices=[i],
169
- msg=f"Empty/whitespace source_span on create op {i}",
170
- )
171
- )
172
- return violations
173
-
174
-
175
- def inv_K_deletes_without_creates(ctx: ExtractionCtx) -> list[Violation]: # noqa: N802
176
- """Plan with deletes but no creates is unusual — worth logging."""
177
- has_delete = any(
178
- isinstance(op, (DeleteUserProfileOp, DeleteUserPlaybookOp)) for op in ctx.plan
179
- )
180
- has_create = any(
181
- isinstance(op, (CreateUserProfileOp, CreateUserPlaybookOp)) for op in ctx.plan
182
- )
183
- if has_delete and not has_create:
184
- indices = [
185
- i
186
- for i, op in enumerate(ctx.plan)
187
- if isinstance(op, (DeleteUserProfileOp, DeleteUserPlaybookOp))
188
- ]
189
- return [
190
- Violation(
191
- code="K",
192
- severity="soft",
193
- affected_op_indices=indices,
194
- msg="Plan contains deletes without any matching creates",
195
- )
196
- ]
197
- return []
198
-
199
-
200
- SOFT_INVARIANTS = (
201
- inv_E_no_duplicate_creates,
202
- inv_H_source_span_present,
203
- inv_K_deletes_without_creates,
204
- )
205
-
206
-
207
- # --- Oscillation resolver ---
208
-
209
-
210
- def resolve_tentative_oscillations(plan: list) -> set[int]:
211
- """Return plan indices to drop: create+delete-tentative pairs cancel.
212
-
213
- When the agent creates an entity (issuing a tentative_id) and later
214
- deletes that same tentative_id within the same plan, both ops are
215
- dropped before invariants fire. This is the "oscillated self-correction"
216
- pattern — the agent changed its mind mid-run.
217
-
218
- The tentative_id format is ``tentative::<kind>::<plan_index_at_issue_time>``,
219
- matching ``_next_tentative_id`` in tools.py which uses ``len(ctx.plan)``
220
- (the plan length BEFORE the op is appended, i.e. the future index of the op).
221
-
222
- Args:
223
- plan: The accumulated list of PlanOp instances from ctx.plan.
224
-
225
- Returns:
226
- Set of plan indices to exclude from apply. Both the create and the
227
- delete are dropped when a matching pair is found.
228
- """
229
- drop: set[int] = set()
230
- pending_creates: dict[str, int] = {}
231
- for i, op in enumerate(plan):
232
- if isinstance(op, CreateUserProfileOp):
233
- tentative_id = f"tentative::profile::{i}"
234
- pending_creates[tentative_id] = i
235
- elif isinstance(op, CreateUserPlaybookOp):
236
- tentative_id = f"tentative::user_playbook::{i}"
237
- pending_creates[tentative_id] = i
238
- elif isinstance(op, (DeleteUserProfileOp, DeleteUserPlaybookOp)):
239
- if op.id.startswith("tentative::") and op.id in pending_creates:
240
- drop.add(pending_creates.pop(op.id))
241
- drop.add(i)
242
- return drop
243
-
244
-
245
- # --- commit_plan ---
246
-
247
-
248
- def commit_plan(
249
- ctx: ExtractionCtx,
250
- storage: object,
251
- *,
252
- outcome: str, # Literal["finish_tool","max_steps","error"]
253
- ) -> CommitResult:
254
- """Run all invariants, then apply surviving ops atomically.
255
-
256
- Args:
257
- ctx: Populated ExtractionCtx from the agent loop.
258
- storage: BaseStorage handle for apply.
259
- outcome: How the loop terminated.
260
-
261
- Returns:
262
- CommitResult containing applied ops + all violations (hard + soft).
263
- """
264
- # Error outcome — discard everything, do not apply
265
- if outcome == "error":
266
- return CommitResult(applied=[], violations=[], outcome="error")
267
-
268
- violations: list[Violation] = []
269
- for check in HARD_INVARIANTS:
270
- violations.extend(check(ctx))
271
- for check in SOFT_INVARIANTS:
272
- violations.extend(check(ctx))
273
-
274
- dropped: set[int] = set()
275
- # Oscillation resolver runs first: matching create+delete-tentative pairs
276
- # cancel before invariants decide what to keep.
277
- dropped.update(resolve_tentative_oscillations(ctx.plan))
278
- for v in violations:
279
- if v.severity == "hard":
280
- dropped.update(v.affected_op_indices)
281
-
282
- ops_to_apply = [op for i, op in enumerate(ctx.plan) if i not in dropped]
283
-
284
- for v in violations:
285
- logger.info(
286
- "invariant_violation user_id=%s code=%s severity=%s op_indices=%s msg=%s",
287
- ctx.user_id,
288
- v.code,
289
- v.severity,
290
- v.affected_op_indices,
291
- v.msg,
292
- )
293
-
294
- # Delegate actual storage writes to the tool-handler module (Task 5 wires this in).
295
- # Lazy import so Task 3 can land before tools.py exists.
296
- from reflexio.server.services.extraction.tools import (
297
- apply_plan_op, # noqa: PLC0415 # type: ignore[import-not-found]
298
- )
299
-
300
- for op in ops_to_apply:
301
- apply_plan_op(op, storage, ctx)
302
-
303
- return CommitResult(applied=ops_to_apply, violations=violations, outcome=outcome) # type: ignore[arg-type]
@@ -1,138 +0,0 @@
1
- """Plan-op types, ExtractionCtx, HandlerBundle, and commit-result types for the agentic-v2 pipeline.
2
-
3
- Tool handlers append PlanOp instances to ``ctx.plan`` rather than hitting
4
- storage directly. A deterministic commit stage at ``finish`` (or on
5
- ``max_steps``) runs invariants and applies the valid ops atomically.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- from dataclasses import dataclass, field
11
- from typing import Annotated, Literal
12
-
13
- from pydantic import BaseModel, ConfigDict, Field
14
-
15
- # Mirrors ProfileTimeToLive — kept as Literal to avoid circular import on enum.
16
- ProfileTTL = Literal[
17
- "one_day", "one_week", "one_month", "one_quarter", "one_year", "infinity"
18
- ]
19
-
20
- PlaybookStrength = Literal["hard", "soft"]
21
-
22
-
23
- class _BasePlanOp(BaseModel):
24
- """Base class for all PlanOp variants. Discriminated union via ``op``."""
25
-
26
- model_config = ConfigDict(frozen=True)
27
-
28
-
29
- class CreateUserProfileOp(_BasePlanOp):
30
- op: Literal["create_user_profile"] = "create_user_profile"
31
- content: Annotated[str, Field(min_length=1)]
32
- ttl: ProfileTTL
33
- source_span: Annotated[str, Field(min_length=1)]
34
-
35
-
36
- class DeleteUserProfileOp(_BasePlanOp):
37
- op: Literal["delete_user_profile"] = "delete_user_profile"
38
- id: Annotated[str, Field(min_length=1)]
39
-
40
-
41
- class CreateUserPlaybookOp(_BasePlanOp):
42
- op: Literal["create_user_playbook"] = "create_user_playbook"
43
- trigger: Annotated[str, Field(min_length=1)]
44
- content: Annotated[str, Field(min_length=1)]
45
- rationale: str = ""
46
- strength: PlaybookStrength = "soft"
47
- source_span: Annotated[str, Field(min_length=1)]
48
-
49
-
50
- class DeleteUserPlaybookOp(_BasePlanOp):
51
- op: Literal["delete_user_playbook"] = "delete_user_playbook"
52
- id: Annotated[str, Field(min_length=1)]
53
-
54
-
55
- PlanOp = Annotated[
56
- CreateUserProfileOp
57
- | DeleteUserProfileOp
58
- | CreateUserPlaybookOp
59
- | DeleteUserPlaybookOp,
60
- Field(discriminator="op"),
61
- ]
62
-
63
-
64
- @dataclass
65
- class ExtractionCtx:
66
- """Per-run state for the extraction agent.
67
-
68
- Attributes:
69
- user_id: Authenticated user the run is scoped to.
70
- agent_version: Agent version from the active config.
71
- request_id: Source publish_interaction request UUID — embedded into
72
- every profile/playbook this run creates so retrieval can trace
73
- back to the originating session. Empty string when called from
74
- test contexts that don't have a publish request.
75
- plan: Accumulated PlanOps awaiting commit.
76
- known_ids: Ids the agent has legitimately seen (from search/get/create
77
- handlers). Invariant B checks delete ids against this set.
78
- search_count: Number of search_* tool calls. Invariant A gates on this.
79
- finished: True once the agent calls the ``finish`` tool.
80
- """
81
-
82
- user_id: str
83
- agent_version: str
84
- request_id: str = ""
85
- plan: list = field(
86
- default_factory=list
87
- ) # list[PlanOp] — type-erased to avoid forward-ref issues
88
- known_ids: set[str] = field(default_factory=set)
89
- search_count: int = 0
90
- finished: bool = False
91
- search_answer: str | None = None
92
- agent_playbook_status_filter: list[str] | None = None
93
- # Compressed rehydration excerpts captured by `read_session_text` calls
94
- # during the agent loop. Surfaced verbatim on the response so callers can
95
- # include them in downstream context without going through the search
96
- # agent's natural-language `finish(answer=…)` synthesis (which paraphrases
97
- # operands and loses fidelity).
98
- rehydrated_excerpts: list[str] = field(default_factory=list)
99
-
100
-
101
- @dataclass(slots=True)
102
- class HandlerBundle:
103
- """Glue so tool handlers can access shared services through one param.
104
-
105
- The run_tool_loop primitive passes a single ``ctx`` param to tool handlers;
106
- handlers in tools.py need access to BaseStorage and an ExtractionCtx. A few
107
- handlers (e.g., the rehydration tool) additionally call back into the LLM
108
- layer for in-tool denoising — those receive ``llm_client`` and
109
- ``prompt_manager`` here. The tool-loop caller builds one of these before
110
- driving the loop.
111
-
112
- Args:
113
- storage: BaseStorage handle.
114
- ctx: ExtractionCtx with per-run state.
115
- llm_client: Optional LiteLLMClient for in-tool LLM calls (e.g.
116
- compression). ``None`` in test paths that don't exercise tools
117
- requiring LLM completions.
118
- prompt_manager: Optional PromptManager for rendering in-tool prompts.
119
- Same ``None`` semantics as ``llm_client``.
120
- """
121
-
122
- storage: object
123
- ctx: ExtractionCtx
124
- llm_client: object | None = None
125
- prompt_manager: object | None = None
126
-
127
-
128
- class Violation(BaseModel):
129
- code: Literal["A", "B", "D", "E", "F", "H", "J", "K"]
130
- severity: Literal["hard", "soft"]
131
- affected_op_indices: list[int]
132
- msg: str
133
-
134
-
135
- class CommitResult(BaseModel):
136
- applied: list[PlanOp]
137
- violations: list[Violation]
138
- outcome: Literal["finish_tool", "max_steps", "error"]