claude-smart 0.2.45 → 0.2.47

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 (353) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +47 -10
  3. package/bin/claude-smart.js +535 -23
  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 +27 -6
  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/layout.tsx +20 -0
  16. package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
  17. package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
  18. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
  19. package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
  20. package/plugin/dashboard/lib/reflexio-client.ts +16 -0
  21. package/plugin/dashboard/lib/status.ts +10 -3
  22. package/plugin/dashboard/lib/types.ts +18 -6
  23. package/plugin/hooks/codex-hooks.json +0 -1
  24. package/plugin/opencode/assistant-buffer.ts +108 -0
  25. package/plugin/opencode/dist/assistant-buffer.js +96 -0
  26. package/plugin/opencode/dist/internal.js +12 -0
  27. package/plugin/opencode/dist/payload.js +85 -0
  28. package/plugin/opencode/dist/server.mjs +232 -0
  29. package/plugin/opencode/internal.ts +18 -0
  30. package/plugin/opencode/package.json +3 -0
  31. package/plugin/opencode/payload.ts +90 -0
  32. package/plugin/opencode/server.mts +251 -0
  33. package/plugin/opencode/tsconfig.json +13 -0
  34. package/plugin/pyproject.toml +25 -4
  35. package/plugin/scripts/_lib.sh +5 -1
  36. package/plugin/scripts/backend-service.sh +22 -2
  37. package/plugin/scripts/ensure-plugin-root.sh +4 -4
  38. package/plugin/scripts/hook_entry.sh +1 -1
  39. package/plugin/scripts/opencode-claude-compat +4 -0
  40. package/plugin/scripts/opencode-claude-compat.cmd +3 -0
  41. package/plugin/scripts/opencode-claude-compat.js +225 -0
  42. package/plugin/scripts/smart-install.sh +17 -9
  43. package/plugin/src/README.md +1 -1
  44. package/plugin/src/claude_smart/cli.py +498 -12
  45. package/plugin/src/claude_smart/context_format.py +9 -9
  46. package/plugin/src/claude_smart/cs_cite.py +66 -19
  47. package/plugin/src/claude_smart/env_config.py +12 -4
  48. package/plugin/src/claude_smart/events/session_start.py +26 -7
  49. package/plugin/src/claude_smart/events/stop.py +1 -1
  50. package/plugin/src/claude_smart/ids.py +1 -1
  51. package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
  52. package/plugin/src/claude_smart/runtime.py +7 -1
  53. package/plugin/uv.lock +5 -5
  54. package/plugin/vendor/reflexio/.env.example +54 -30
  55. package/plugin/vendor/reflexio/README.md +17 -11
  56. package/plugin/vendor/reflexio/pyproject.toml +13 -1
  57. package/plugin/vendor/reflexio/reflexio/README.md +4 -1
  58. package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
  59. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
  60. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
  63. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
  64. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +212 -52
  65. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
  66. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
  67. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
  68. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
  69. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
  70. package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
  71. package/plugin/vendor/reflexio/reflexio/client/client.py +154 -13
  72. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
  142. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
  143. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
  144. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
  145. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +246 -10
  146. package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
  147. package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
  148. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +17 -5
  149. package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
  150. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +224 -21
  151. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
  152. package/plugin/vendor/reflexio/reflexio/lib/_search.py +52 -25
  153. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +11 -1
  154. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
  155. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
  156. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
  157. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
  158. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
  159. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
  160. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
  161. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +97 -3
  162. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
  163. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
  164. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +104 -7
  165. package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
  166. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
  167. package/plugin/vendor/reflexio/reflexio/server/README.md +58 -33
  168. package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
  169. package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
  170. package/plugin/vendor/reflexio/reflexio/server/api.py +899 -103
  171. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
  172. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +4 -2
  173. package/plugin/vendor/reflexio/reflexio/server/auth.py +90 -0
  174. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
  175. package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
  176. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
  177. package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
  178. package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
  179. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
  180. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
  181. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
  182. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
  183. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
  184. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
  185. package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
  186. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
  187. package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
  188. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
  189. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
  190. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
  191. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
  192. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
  193. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
  194. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
  195. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
  196. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
  197. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
  198. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +278 -0
  199. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
  200. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
  201. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
  202. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
  203. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
  204. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
  205. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
  206. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
  207. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
  208. package/plugin/vendor/reflexio/reflexio/server/services/README.md +28 -11
  209. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
  210. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
  211. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
  212. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
  213. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
  214. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
  215. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
  216. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
  217. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
  218. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
  219. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
  220. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
  221. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
  222. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
  223. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
  224. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
  225. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
  226. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
  227. package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
  228. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
  229. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
  230. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
  231. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +47 -16
  232. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
  233. package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
  234. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
  235. package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
  236. package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
  237. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +219 -0
  238. package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
  239. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
  240. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +18 -11
  241. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
  242. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
  243. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +334 -178
  244. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
  245. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
  246. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
  247. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
  248. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
  249. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
  250. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
  251. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
  252. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
  253. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
  254. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
  255. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +31 -22
  256. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
  257. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
  258. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
  259. package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
  260. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
  261. package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
  262. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
  263. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +99 -6
  264. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
  265. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
  266. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
  267. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +31 -0
  268. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
  269. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
  270. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +16 -2
  271. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
  272. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +644 -197
  273. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +106 -119
  274. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
  275. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +630 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
  277. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +31 -1369
  278. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +756 -210
  279. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +148 -34
  280. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
  281. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
  282. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
  283. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
  284. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
  285. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
  286. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
  287. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
  288. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +150 -23
  289. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
  290. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
  291. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +40 -57
  292. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
  293. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
  294. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
  295. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +4 -677
  296. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +134 -1
  297. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +62 -0
  298. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
  299. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
  300. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
  301. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
  302. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
  303. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
  304. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
  305. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
  306. package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
  307. package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
  308. package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
  309. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
  310. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +372 -34
  311. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
  312. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
  313. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
  314. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
  315. package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
  316. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
  317. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
  318. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
  319. package/scripts/setup-claude-smart.sh +8 -3
  320. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
  321. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
  322. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
  323. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
  324. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
  325. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
  326. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
  327. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
  328. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
  329. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
  330. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
  331. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
  332. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
  333. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
  334. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
  335. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
  336. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
  337. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
  338. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
  339. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
  340. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
  341. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
  342. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
  343. package/plugin/vendor/reflexio/reflexio/server/_auth.py +0 -25
  344. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
  345. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
  346. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
  347. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
  348. /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
  349. /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
  350. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
  351. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
  352. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
  353. /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
@@ -4,6 +4,7 @@ import hashlib
4
4
  import logging
5
5
  import os
6
6
  import time
7
+ import uuid
7
8
  from collections.abc import Sequence
8
9
  from typing import TYPE_CHECKING
9
10
 
@@ -12,13 +13,9 @@ if TYPE_CHECKING:
12
13
 
13
14
  from reflexio.models.api_schema.service_schemas import (
14
15
  AgentPlaybook,
15
- AgentPlaybookSnapshot,
16
16
  AgentPlaybookSourceWindow,
17
- AgentPlaybookUpdateEntry,
18
- PlaybookAggregationChangeLog,
19
17
  PlaybookStatus,
20
18
  UserPlaybook,
21
- agent_playbook_to_snapshot,
22
19
  )
23
20
  from reflexio.models.config_schema import (
24
21
  SINGLETON_USER_PLAYBOOK_NAME,
@@ -27,15 +24,21 @@ from reflexio.models.config_schema import (
27
24
  from reflexio.server.api_endpoints.request_context import RequestContext
28
25
  from reflexio.server.llm.litellm_client import LiteLLMClient
29
26
  from reflexio.server.services.operation_state_utils import OperationStateManager
27
+ from reflexio.server.services.playbook.aggregation_prompt_processing import (
28
+ AggregationPromptProcessingContext,
29
+ AggregationPromptProcessor,
30
+ )
30
31
  from reflexio.server.services.playbook.playbook_service_constants import (
31
32
  PlaybookServiceConstants,
32
33
  )
33
34
  from reflexio.server.services.playbook.playbook_service_utils import (
34
35
  PlaybookAggregationOutput,
35
36
  PlaybookAggregatorRequest,
37
+ StructuredPlaybookContent,
36
38
  ensure_playbook_content,
37
39
  )
38
40
  from reflexio.server.services.service_utils import log_model_response
41
+ from reflexio.server.tracing import capture_anomaly, sentry_tags
39
42
  from reflexio.server.usage_metrics import record_usage_event
40
43
 
41
44
  logger = logging.getLogger(__name__)
@@ -52,17 +55,25 @@ class PlaybookAggregator:
52
55
  llm_client: LiteLLMClient,
53
56
  request_context: RequestContext,
54
57
  agent_version: str,
58
+ aggregation_prompt_processor: AggregationPromptProcessor | None = None,
55
59
  ) -> None:
56
60
  self.client = llm_client
57
61
  self.storage = request_context.storage
58
62
  self.configurator = request_context.configurator
59
63
  self.request_context = request_context
60
64
  self.agent_version = agent_version
65
+ self.aggregation_prompt_processor = aggregation_prompt_processor
61
66
 
62
67
  # ===============================
63
68
  # private methods - operation state
64
69
  # ===============================
65
70
 
71
+ @staticmethod
72
+ def _format_prompt_extra_instructions(instructions: str | None) -> str:
73
+ if not instructions or not instructions.strip():
74
+ return ""
75
+ return f"{instructions.strip()}\n"
76
+
66
77
  def _create_state_manager(self) -> OperationStateManager:
67
78
  """
68
79
  Create an OperationStateManager for the playbook aggregator.
@@ -103,7 +114,7 @@ class PlaybookAggregator:
103
114
  # Count user playbooks with ID greater than last processed using efficient count query
104
115
  # Only count current user playbooks (status=None), not archived or pending ones.
105
116
  # Singleton aggregation operates on the user's whole playbook set — no name filter.
106
- new_count = self.storage.count_user_playbooks( # type: ignore[reportOptionalMemberAccess]
117
+ new_count = self.storage.count_user_playbooks( # pyright: ignore[reportOptionalMemberAccess]
107
118
  min_user_playbook_id=last_processed_id,
108
119
  agent_version=self.agent_version,
109
120
  status_filter=[None],
@@ -194,6 +205,98 @@ class PlaybookAggregator:
194
205
  blocks.append("\n".join(lines))
195
206
  return "\n\n".join(blocks) if blocks else "(No playbook items)"
196
207
 
208
+ def _preprocess_prompt_field(
209
+ self,
210
+ text: str | None,
211
+ shared_state: dict[str, object],
212
+ processing_context: AggregationPromptProcessingContext | None = None,
213
+ ) -> str | None:
214
+ if text is None or self.aggregation_prompt_processor is None:
215
+ return text
216
+ result = self.aggregation_prompt_processor.preprocess_prompt_text(
217
+ text,
218
+ shared_state=shared_state,
219
+ context=processing_context,
220
+ )
221
+ if processing_context is not None and (result.changed or result.text != text):
222
+ processing_context.changed = True
223
+ return result.text
224
+
225
+ def _preprocess_user_playbook_for_prompt(
226
+ self,
227
+ playbook: UserPlaybook,
228
+ shared_state: dict[str, object],
229
+ processing_context: AggregationPromptProcessingContext | None = None,
230
+ ) -> UserPlaybook:
231
+ if self.aggregation_prompt_processor is None:
232
+ return playbook
233
+ return playbook.model_copy(
234
+ update={
235
+ "content": self._preprocess_prompt_field(
236
+ playbook.content, shared_state, processing_context
237
+ )
238
+ or "",
239
+ "trigger": self._preprocess_prompt_field(
240
+ playbook.trigger, shared_state, processing_context
241
+ ),
242
+ "rationale": self._preprocess_prompt_field(
243
+ playbook.rationale, shared_state, processing_context
244
+ ),
245
+ }
246
+ )
247
+
248
+ def _aggregation_prompt_extra_instructions_for_context(
249
+ self,
250
+ processing_context: AggregationPromptProcessingContext | None,
251
+ ) -> str:
252
+ if (
253
+ processing_context is None
254
+ or not processing_context.changed
255
+ or self.aggregation_prompt_processor is None
256
+ ):
257
+ return ""
258
+
259
+ context_instructions = self.aggregation_prompt_processor.prompt_instructions(
260
+ processing_context
261
+ )
262
+ if not isinstance(context_instructions, str):
263
+ context_instructions = None
264
+ return self._format_prompt_extra_instructions(context_instructions)
265
+
266
+ def _postprocess_aggregation_output(
267
+ self,
268
+ value: object,
269
+ processing_context: AggregationPromptProcessingContext | None = None,
270
+ ) -> tuple[object, int]:
271
+ if self.aggregation_prompt_processor is None:
272
+ return value, 0
273
+ result = self.aggregation_prompt_processor.postprocess_aggregation_output(
274
+ value,
275
+ context=processing_context,
276
+ )
277
+ return result.value, result.artifacts_removed
278
+
279
+ def _postprocess_aggregation_response(
280
+ self,
281
+ response: PlaybookAggregationOutput,
282
+ processing_context: AggregationPromptProcessingContext | None = None,
283
+ ) -> tuple[PlaybookAggregationOutput, int]:
284
+ processed, artifact_count = self._postprocess_aggregation_output(
285
+ response,
286
+ processing_context,
287
+ )
288
+ if not isinstance(processed, PlaybookAggregationOutput):
289
+ return response, 0
290
+ return processed, artifact_count
291
+
292
+ def _record_postprocessing_artifacts(self, artifact_count: int) -> None:
293
+ if artifact_count <= 0:
294
+ return
295
+ logger.warning(
296
+ "Post-processed %d residual artifacts in aggregated playbook output",
297
+ artifact_count,
298
+ )
299
+
197
300
  @staticmethod
198
301
  def _get_direction_key(fb: UserPlaybook) -> str:
199
302
  """
@@ -465,102 +568,6 @@ class PlaybookAggregator:
465
568
 
466
569
  return changed_clusters, playbook_ids_to_archive
467
570
 
468
- def _build_change_log(
469
- self,
470
- playbook_name: str,
471
- full_archive: bool,
472
- before_playbooks_by_id: dict[int, AgentPlaybook],
473
- saved_playbooks: list[AgentPlaybook],
474
- archived_playbook_ids: list[int],
475
- prev_fingerprints: dict,
476
- ) -> PlaybookAggregationChangeLog:
477
- """Build a PlaybookAggregationChangeLog from the aggregation run results.
478
-
479
- Args:
480
- playbook_name: The playbook name being aggregated
481
- full_archive: Whether this was a full archive (rerun/first run)
482
- before_playbooks_by_id: Snapshot of playbooks before archiving, keyed by playbook_id
483
- saved_playbooks: Newly saved playbooks from this run
484
- archived_playbook_ids: AgentPlaybook IDs that were selectively archived (incremental mode)
485
- prev_fingerprints: Previous cluster fingerprints (empty for full archive)
486
-
487
- Returns:
488
- PlaybookAggregationChangeLog with added/removed/updated lists populated
489
- """
490
- added: list[AgentPlaybookSnapshot] = []
491
- removed: list[AgentPlaybookSnapshot] = []
492
- updated: list[AgentPlaybookUpdateEntry] = []
493
-
494
- if full_archive:
495
- # No 1:1 mapping — all old playbooks are removed, all new are added
496
- removed = [
497
- agent_playbook_to_snapshot(fb) for fb in before_playbooks_by_id.values()
498
- ]
499
- added = [agent_playbook_to_snapshot(fb) for fb in saved_playbooks if fb]
500
- else:
501
- # Incremental mode: map old playbook_ids to new playbooks via fingerprints
502
- # Build a set of old playbook_ids that were archived
503
- archived_id_set = set(archived_playbook_ids)
504
-
505
- # Build mapping: old_playbook_id -> new_playbook_id via fingerprint changes
506
- # prev_fingerprints maps fp_hash -> {playbook_id, user_playbook_ids}
507
- # new_fingerprints maps fp_hash -> {playbook_id, user_playbook_ids}
508
- # If an old fingerprint disappeared and a new one appeared, and
509
- # the old fp had a playbook_id in archived_id_set, we can try to pair them.
510
- # However, without a direct cluster-level old->new mapping, we use a simpler approach:
511
- # archived playbooks that have a corresponding new playbook (by position in saved list) are updates.
512
-
513
- # Collect old playbook_ids from disappeared fingerprints
514
- old_fp_playbook_ids = {}
515
- for fp, fp_data in prev_fingerprints.items():
516
- fid = fp_data.get("agent_playbook_id")
517
- if fid is not None and fid in archived_id_set:
518
- old_fp_playbook_ids[fid] = fp
519
-
520
- # For each saved playbook, try to match with an archived old playbook
521
- matched_old_ids: set[int] = set()
522
- for saved_fb in saved_playbooks:
523
- if not saved_fb:
524
- continue
525
- # Try to find an old playbook from the archived set to pair with
526
- paired_old_id = None
527
- for old_id in list(old_fp_playbook_ids.keys()):
528
- if old_id not in matched_old_ids:
529
- paired_old_id = old_id
530
- matched_old_ids.add(old_id)
531
- break
532
-
533
- if (
534
- paired_old_id is not None
535
- and paired_old_id in before_playbooks_by_id
536
- ):
537
- updated.append(
538
- AgentPlaybookUpdateEntry(
539
- before=agent_playbook_to_snapshot(
540
- before_playbooks_by_id[paired_old_id]
541
- ),
542
- after=agent_playbook_to_snapshot(saved_fb),
543
- )
544
- )
545
- else:
546
- added.append(agent_playbook_to_snapshot(saved_fb))
547
-
548
- # Remaining archived playbooks that weren't paired are removals
549
- for old_id in archived_id_set:
550
- if old_id not in matched_old_ids and old_id in before_playbooks_by_id:
551
- removed.append(
552
- agent_playbook_to_snapshot(before_playbooks_by_id[old_id])
553
- )
554
-
555
- return PlaybookAggregationChangeLog(
556
- playbook_name=playbook_name,
557
- agent_version=self.agent_version,
558
- run_mode="full_archive" if full_archive else "incremental",
559
- added_agent_playbooks=added,
560
- removed_agent_playbooks=removed,
561
- updated_agent_playbooks=updated,
562
- )
563
-
564
571
  # ===============================
565
572
  # public methods
566
573
  # ===============================
@@ -572,6 +579,8 @@ class PlaybookAggregator:
572
579
  dict: Aggregation stats with keys: clusters_found, user_playbooks_processed, playbooks_generated, skipped (optional)
573
580
  """
574
581
  aggregation_start = time.perf_counter()
582
+ # Stable id for this aggregation run — groups all lineage events produced below.
583
+ _run_id = str(uuid.uuid4())
575
584
  _empty_stats = {
576
585
  "clusters_found": 0,
577
586
  "user_playbooks_processed": 0,
@@ -667,6 +676,12 @@ class PlaybookAggregator:
667
676
  playbook_name,
668
677
  self.agent_version,
669
678
  )
679
+ logger.info(
680
+ "Aggregation prompt processor: %s",
681
+ type(self.aggregation_prompt_processor).__name__
682
+ if self.aggregation_prompt_processor is not None
683
+ else "disabled",
684
+ )
670
685
 
671
686
  # Get existing APPROVED and PENDING playbooks before archiving (to pass to LLM for deduplication).
672
687
  # Singleton aggregation pulls the user's whole set — no name filter.
@@ -694,11 +709,6 @@ class PlaybookAggregator:
694
709
  )
695
710
  clusters = self.get_clusters(user_playbooks, playbook_aggregator_config)
696
711
 
697
- # Capture all current playbooks before archiving (for change log)
698
- before_playbooks_by_id: dict[int, AgentPlaybook] = {
699
- fb.agent_playbook_id: fb for fb in existing_playbooks
700
- }
701
-
702
712
  # Determine which clusters changed (skip for rerun)
703
713
  mgr = self._create_state_manager()
704
714
  archived_playbook_ids = []
@@ -764,10 +774,6 @@ class PlaybookAggregator:
764
774
  len(archived_playbook_ids),
765
775
  )
766
776
 
767
- # Selectively archive only playbooks from changed/disappeared clusters
768
- if archived_playbook_ids:
769
- self.storage.archive_agent_playbooks_by_ids(archived_playbook_ids) # type: ignore[reportOptionalMemberAccess]
770
-
771
777
  try:
772
778
  # Emit the started event inside the protected block so any failure
773
779
  # from here on is paired with an aggregation_failed event.
@@ -789,6 +795,37 @@ class PlaybookAggregator:
789
795
  )
790
796
  new_playbooks = [playbook for playbook, _ in generated_pairs]
791
797
 
798
+ previous_fingerprints_for_changed_clusters = {}
799
+ changed_fps_by_previous_fp = {}
800
+ changed_fps_with_replacements = set()
801
+ previous_playbook_id_by_fp = {}
802
+ if not playbook_aggregator_request.rerun and prev_fingerprints:
803
+ for cluster_playbooks in changed_clusters.values():
804
+ fp = self._compute_cluster_fingerprint(cluster_playbooks)
805
+ current_user_ids = {
806
+ fb.user_playbook_id
807
+ for fb in cluster_playbooks
808
+ if fb.user_playbook_id is not None
809
+ }
810
+ matched_prev_fingerprints = {
811
+ prev_fp: fp_data
812
+ for prev_fp, fp_data in prev_fingerprints.items()
813
+ if fp_data.get("agent_playbook_id") is not None
814
+ and current_user_ids
815
+ & set(fp_data.get("user_playbook_ids") or [])
816
+ }
817
+ if matched_prev_fingerprints:
818
+ previous_fingerprints_for_changed_clusters[fp] = (
819
+ matched_prev_fingerprints
820
+ )
821
+ for prev_fp, fp_data in matched_prev_fingerprints.items():
822
+ changed_fps_by_previous_fp.setdefault(prev_fp, set()).add(
823
+ fp
824
+ )
825
+ playbook_id = fp_data.get("agent_playbook_id")
826
+ if playbook_id is not None:
827
+ previous_playbook_id_by_fp[prev_fp] = playbook_id
828
+
792
829
  # Lazy archive: only full-archive when the LLM produced replacements.
793
830
  # Skipping the archive when new_playbooks is empty preserves existing
794
831
  # PENDING/APPROVED playbooks that the LLM identified as duplicates.
@@ -806,17 +843,12 @@ class PlaybookAggregator:
806
843
  )
807
844
  full_archive = False
808
845
 
809
- # Save playbooks (returns playbooks with playbook_id populated)
810
- saved_playbooks = self.storage.save_agent_playbooks(new_playbooks) # type: ignore[reportOptionalMemberAccess]
811
-
812
846
  # Build new fingerprint state
813
847
  new_fingerprints = {}
814
848
 
815
849
  if not playbook_aggregator_request.rerun:
816
850
  # Carry forward unchanged fingerprints from previous state
817
- prev_fps = mgr.get_cluster_fingerprints(
818
- name=playbook_name, version=self.agent_version
819
- )
851
+ prev_fps = prev_fingerprints
820
852
  current_fp_set = set()
821
853
  for cluster_playbooks in clusters.values():
822
854
  fp = self._compute_cluster_fingerprint(cluster_playbooks)
@@ -837,31 +869,47 @@ class PlaybookAggregator:
837
869
  }
838
870
  )
839
871
 
840
- # Initialize changed cluster fingerprints before assigning saved IDs
841
- # below. ``generated_pairs`` preserves the exact source cluster for
842
- # every non-duplicate playbook the LLM produced.
843
- for cluster_playbooks in changed_clusters.values():
844
- fp = self._compute_cluster_fingerprint(cluster_playbooks)
845
- raw_ids = sorted(fb.user_playbook_id for fb in cluster_playbooks)
846
-
847
- new_fingerprints[fp] = {
848
- "agent_playbook_id": None,
849
- "user_playbook_ids": raw_ids,
850
- }
851
-
852
- saved_playbook_list = list(saved_playbooks)
853
-
854
- # Assign saved playbook ids to the exact source cluster that generated them.
855
- for saved_fb, (_, cluster_playbooks) in zip(
856
- saved_playbook_list, generated_pairs, strict=False
857
- ):
872
+ saved_playbook_list: list[AgentPlaybook] = []
873
+ selective_supersede_playbook_ids = set()
874
+ replaced_previous_fingerprints = set()
875
+
876
+ # Save each playbook + its aggregate event atomically, then assign
877
+ # fingerprints and source-windows for the saved row.
878
+ for playbook, cluster_playbooks in generated_pairs:
879
+ run_mode = "full_archive" if full_archive else "incremental"
880
+ member_ids = [
881
+ str(fb.user_playbook_id)
882
+ for fb in cluster_playbooks
883
+ if fb.user_playbook_id
884
+ ]
885
+ saved_fb = self.storage.save_agent_playbook_with_aggregate_event( # type: ignore[reportOptionalMemberAccess]
886
+ playbook,
887
+ source_ids=member_ids,
888
+ request_id=_run_id,
889
+ run_mode=run_mode,
890
+ )
891
+ saved_playbook_list.append(saved_fb)
858
892
  if saved_fb and saved_fb.agent_playbook_id:
859
893
  fp_key = self._compute_cluster_fingerprint(cluster_playbooks)
894
+ changed_fps_with_replacements.add(fp_key)
860
895
  raw_ids = sorted(fb.user_playbook_id for fb in cluster_playbooks)
861
896
  new_fingerprints[fp_key] = {
862
897
  "agent_playbook_id": saved_fb.agent_playbook_id,
863
898
  "user_playbook_ids": raw_ids,
864
899
  }
900
+ for prev_fp in previous_fingerprints_for_changed_clusters.get(
901
+ fp_key, {}
902
+ ):
903
+ all_overlapping_clusters_replaced = (
904
+ changed_fps_by_previous_fp.get(prev_fp, set()).issubset(
905
+ changed_fps_with_replacements
906
+ )
907
+ )
908
+ playbook_id = previous_playbook_id_by_fp.get(prev_fp)
909
+ if all_overlapping_clusters_replaced:
910
+ replaced_previous_fingerprints.add(prev_fp)
911
+ if playbook_id is not None:
912
+ selective_supersede_playbook_ids.add(playbook_id)
865
913
  self.storage.set_source_windows_for_agent_playbook( # type: ignore[reportOptionalMemberAccess]
866
914
  saved_fb.agent_playbook_id,
867
915
  [
@@ -876,6 +924,33 @@ class PlaybookAggregator:
876
924
  ],
877
925
  )
878
926
 
927
+ # Changed clusters that did not get a replacement keep their previous
928
+ # fingerprint/playbook mapping so a later successful replacement can
929
+ # supersede the old playbook. Brand-new duplicate clusters still get
930
+ # a None marker to avoid repeated LLM calls for the same fingerprint.
931
+ for cluster_playbooks in changed_clusters.values():
932
+ fp = self._compute_cluster_fingerprint(cluster_playbooks)
933
+ if fp in new_fingerprints:
934
+ continue
935
+
936
+ previous_for_cluster = previous_fingerprints_for_changed_clusters.get(
937
+ fp, {}
938
+ )
939
+ preserved_previous = {
940
+ prev_fp: fp_data
941
+ for prev_fp, fp_data in previous_for_cluster.items()
942
+ if prev_fp not in replaced_previous_fingerprints
943
+ }
944
+ if preserved_previous:
945
+ new_fingerprints.update(preserved_previous)
946
+ continue
947
+
948
+ raw_ids = sorted(fb.user_playbook_id for fb in cluster_playbooks)
949
+ new_fingerprints[fp] = {
950
+ "agent_playbook_id": None,
951
+ "user_playbook_ids": raw_ids,
952
+ }
953
+
879
954
  # Store fingerprints in operation state
880
955
  mgr.update_cluster_fingerprints(
881
956
  name=playbook_name,
@@ -886,39 +961,70 @@ class PlaybookAggregator:
886
961
  # Update operation state with the highest user_playbook_id processed
887
962
  self._update_operation_state(playbook_name, user_playbooks)
888
963
 
889
- # Build and save change log
890
- try:
891
- change_log = self._build_change_log(
892
- playbook_name=playbook_name,
893
- full_archive=full_archive,
894
- before_playbooks_by_id=before_playbooks_by_id,
895
- saved_playbooks=saved_playbook_list,
896
- archived_playbook_ids=archived_playbook_ids,
897
- prev_fingerprints=(prev_fingerprints if not full_archive else {}),
898
- )
899
- self.storage.add_playbook_aggregation_change_log(change_log) # type: ignore[reportOptionalMemberAccess]
900
- logger.info(
901
- "User playbook aggregation change log for '%s' (agent_version=%s): %d agent playbooks added, %d removed, %d updated",
902
- playbook_name,
903
- self.agent_version,
904
- len(change_log.added_agent_playbooks),
905
- len(change_log.removed_agent_playbooks),
906
- len(change_log.updated_agent_playbooks),
907
- )
908
- except Exception:
909
- logger.exception(
910
- "Failed to save playbook aggregation change log for '%s', continuing",
911
- playbook_name,
964
+ # Remove archived playbooks after successful aggregation. ALWAYS soft-supersede
965
+ # (never hard-delete) so the removal is reconstructable from lineage — mirrors the
966
+ # profile dedup always-soft path (#206).
967
+ archived_ids_without_overlapping_changed_cluster: set[int] = set()
968
+ if new_playbooks and prev_fingerprints:
969
+ archived_id_set = set(archived_playbook_ids)
970
+ for prev_fp, fp_data in prev_fingerprints.items():
971
+ playbook_id = fp_data.get("agent_playbook_id")
972
+ if (
973
+ playbook_id in archived_id_set
974
+ and prev_fp not in changed_fps_by_previous_fp
975
+ ):
976
+ archived_ids_without_overlapping_changed_cluster.add(
977
+ playbook_id
978
+ )
979
+ ids_to_supersede = {
980
+ *selective_supersede_playbook_ids,
981
+ *archived_ids_without_overlapping_changed_cluster,
982
+ }
983
+ if not _run_id:
984
+ # Empty request_id makes the removal unreconstructable (lineage events are keyed
985
+ # on it). Fail loud and skip removal never silently hard-delete.
986
+ capture_anomaly(
987
+ "lineage.aggregation.missing_request_id",
988
+ level="error",
989
+ org_id=self.request_context.org_id,
912
990
  )
913
-
914
- # Delete archived playbooks after successful aggregation
915
- if full_archive:
916
- for name in full_archive_playbook_names:
917
- self.storage.delete_archived_agent_playbooks_by_playbook_name( # type: ignore[reportOptionalMemberAccess]
918
- name, agent_version=self.agent_version
991
+ else:
992
+ try:
993
+ if full_archive:
994
+ for name in full_archive_playbook_names:
995
+ self.storage.supersede_agent_playbooks_by_playbook_name( # type: ignore[reportOptionalMemberAccess]
996
+ name,
997
+ agent_version=self.agent_version,
998
+ request_id=_run_id,
999
+ )
1000
+ elif ids_to_supersede:
1001
+ self.storage.supersede_agent_playbooks_by_ids( # type: ignore[reportOptionalMemberAccess]
1002
+ sorted(ids_to_supersede),
1003
+ request_id=_run_id,
1004
+ )
1005
+ elif archived_playbook_ids:
1006
+ logger.info(
1007
+ "Skipping selective supersede of %s (agent_version=%s): LLM produced 0 new playbooks; existing PENDING/APPROVED playbooks preserved",
1008
+ archived_playbook_ids,
1009
+ self.agent_version,
1010
+ )
1011
+ except Exception:
1012
+ with sentry_tags(
1013
+ subsystem="playbook_aggregation",
1014
+ op="supersede_agent_playbooks",
1015
+ org_id=self.request_context.org_id,
1016
+ request_id=_run_id,
1017
+ ):
1018
+ logger.exception(
1019
+ "Failed to soft-supersede archived agent playbooks (run %s)",
1020
+ _run_id,
1021
+ )
1022
+ capture_anomaly(
1023
+ "lineage.aggregation.supersede_failed",
1024
+ level="error",
1025
+ org_id=self.request_context.org_id,
1026
+ request_id=_run_id,
919
1027
  )
920
- elif archived_playbook_ids:
921
- self.storage.delete_agent_playbooks_by_ids(archived_playbook_ids) # type: ignore[reportOptionalMemberAccess]
922
1028
 
923
1029
  self._enqueue_playbook_optimization(saved_playbook_list)
924
1030
 
@@ -1197,19 +1303,35 @@ class PlaybookAggregator:
1197
1303
  direction_overlap_threshold: float = 0.6,
1198
1304
  ) -> list[tuple[AgentPlaybook, list[UserPlaybook]]]:
1199
1305
  """Generate agent playbooks while preserving their exact source cluster."""
1200
- # Format existing approved playbooks for the prompt
1306
+ new_playbooks: list[tuple[AgentPlaybook, list[UserPlaybook]]] = []
1201
1307
  approved_playbooks_str = (
1202
1308
  "\n".join([f"- {fb.content}" for fb in existing_approved_playbooks])
1203
1309
  if existing_approved_playbooks
1204
1310
  else "None"
1205
1311
  )
1206
-
1207
- new_playbooks: list[tuple[AgentPlaybook, list[UserPlaybook]]] = []
1208
1312
  for cluster_playbooks in clusters.values():
1313
+ shared_state: dict[str, object] = {}
1314
+ processing_context = AggregationPromptProcessingContext(
1315
+ data={
1316
+ "agent_version": self.agent_version,
1317
+ "org_id": self.request_context.org_id,
1318
+ }
1319
+ )
1320
+ if self.aggregation_prompt_processor is None:
1321
+ prompt_cluster_playbooks = cluster_playbooks
1322
+ else:
1323
+ prompt_cluster_playbooks = [
1324
+ self._preprocess_user_playbook_for_prompt(
1325
+ playbook, shared_state, processing_context
1326
+ )
1327
+ for playbook in cluster_playbooks
1328
+ ]
1329
+
1209
1330
  playbook = self._generate_playbook_from_cluster(
1210
- cluster_playbooks,
1331
+ prompt_cluster_playbooks,
1211
1332
  approved_playbooks_str,
1212
1333
  direction_overlap_threshold=direction_overlap_threshold,
1334
+ processing_context=processing_context,
1213
1335
  )
1214
1336
  if playbook is not None:
1215
1337
  new_playbooks.append((playbook, cluster_playbooks))
@@ -1259,6 +1381,7 @@ class PlaybookAggregator:
1259
1381
  cluster_playbooks: list[UserPlaybook],
1260
1382
  existing_approved_playbooks_str: str,
1261
1383
  direction_overlap_threshold: float = 0.6,
1384
+ processing_context: AggregationPromptProcessingContext | None = None,
1262
1385
  ) -> AgentPlaybook | None:
1263
1386
  """
1264
1387
  Generate a playbook from a cluster using structured JSON output.
@@ -1289,14 +1412,21 @@ class PlaybookAggregator:
1289
1412
  # Build content directly as a freeform summary
1290
1413
  content_text = f"When {trigger}, {first_content}."
1291
1414
 
1292
- return AgentPlaybook(
1293
- playbook_name=cluster_playbooks[0].playbook_name,
1294
- agent_version=cluster_playbooks[0].agent_version,
1295
- content=content_text,
1296
- trigger=trigger,
1297
- playbook_status=PlaybookStatus.PENDING,
1298
- playbook_metadata="mock_generated",
1415
+ response = PlaybookAggregationOutput(
1416
+ playbook=StructuredPlaybookContent(
1417
+ content=content_text,
1418
+ trigger=trigger,
1419
+ )
1299
1420
  )
1421
+ response, artifact_count = self._postprocess_aggregation_response(
1422
+ response,
1423
+ processing_context,
1424
+ )
1425
+ self._record_postprocessing_artifacts(artifact_count)
1426
+ playbook = self._process_aggregation_response(response, cluster_playbooks)
1427
+ if playbook is None:
1428
+ return None
1429
+ return playbook.model_copy(update={"playbook_metadata": "mock_generated"})
1300
1430
 
1301
1431
  # Format raw playbooks for prompt using structured format
1302
1432
  raw_playbooks_str = self._format_structured_cluster_input(
@@ -1312,6 +1442,9 @@ class PlaybookAggregator:
1312
1442
  {
1313
1443
  "user_playbooks": raw_playbooks_str,
1314
1444
  "existing_approved_playbooks": existing_approved_playbooks_str,
1445
+ "aggregation_prompt_extra_instructions": self._aggregation_prompt_extra_instructions_for_context(
1446
+ processing_context
1447
+ ),
1315
1448
  },
1316
1449
  ),
1317
1450
  }
@@ -1324,6 +1457,18 @@ class PlaybookAggregator:
1324
1457
  response_format=PlaybookAggregationOutput,
1325
1458
  parse_structured_output=True,
1326
1459
  )
1460
+ if isinstance(response, PlaybookAggregationOutput):
1461
+ response, artifact_count = self._postprocess_aggregation_response(
1462
+ response,
1463
+ processing_context,
1464
+ )
1465
+ self._record_postprocessing_artifacts(artifact_count)
1466
+ else:
1467
+ response, artifact_count = self._postprocess_aggregation_output(
1468
+ response,
1469
+ processing_context,
1470
+ )
1471
+ self._record_postprocessing_artifacts(artifact_count)
1327
1472
  log_model_response(logger, "Aggregation structured response", response)
1328
1473
 
1329
1474
  if not isinstance(response, PlaybookAggregationOutput):
@@ -1335,9 +1480,14 @@ class PlaybookAggregator:
1335
1480
 
1336
1481
  return self._process_aggregation_response(response, cluster_playbooks)
1337
1482
  except Exception as exc:
1483
+ processed_error, artifact_count = self._postprocess_aggregation_output(
1484
+ str(exc),
1485
+ processing_context,
1486
+ )
1487
+ self._record_postprocessing_artifacts(artifact_count)
1338
1488
  logger.error(
1339
1489
  "AgentPlaybook aggregation failed due to %s, returning None.",
1340
- str(exc),
1490
+ processed_error,
1341
1491
  )
1342
1492
  return None
1343
1493
 
@@ -1349,7 +1499,10 @@ class PlaybookAggregator:
1349
1499
 
1350
1500
  Args:
1351
1501
  response: Parsed PlaybookAggregationOutput from LLM
1352
- cluster_playbooks: Original cluster playbooks for metadata
1502
+ cluster_playbooks: Cluster playbooks used only for non-user metadata
1503
+ such as playbook name and agent version. Callers may pass
1504
+ prompt-preprocessed copies here, so this method must not read
1505
+ user-authored fields from them.
1353
1506
 
1354
1507
  Returns:
1355
1508
  AgentPlaybook or None if no playbook should be generated
@@ -1365,6 +1518,9 @@ class PlaybookAggregator:
1365
1518
  # content is always the LLM's freeform summary;
1366
1519
  # fall back to formatted structured fields for backward compatibility
1367
1520
  playbook_content = ensure_playbook_content(structured.content, structured)
1521
+ if not playbook_content.strip():
1522
+ logger.info("Aggregated playbook has no valid content, skipping")
1523
+ return None
1368
1524
  logger.info(
1369
1525
  "Aggregated playbook content (freeform): %.200s",
1370
1526
  playbook_content,