claude-smart 0.2.48 → 0.2.50

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 (193) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.md +11 -40
  3. package/bin/claude-smart.js +393 -55
  4. package/package.json +3 -2
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/.coverage +0 -0
  8. package/plugin/README.md +4 -3
  9. package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +4 -2
  10. package/plugin/dashboard/app/dashboard/page.tsx +6 -1
  11. package/plugin/dashboard/app/preferences/[id]/page.tsx +18 -6
  12. package/plugin/dashboard/app/preferences/page.tsx +32 -35
  13. package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +16 -1
  14. package/plugin/dashboard/app/sessions/page.tsx +2 -0
  15. package/plugin/dashboard/app/skills/page.tsx +65 -50
  16. package/plugin/dashboard/app/skills/project/[id]/page.tsx +17 -8
  17. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +1 -6
  18. package/plugin/dashboard/components/common/host-badge.tsx +118 -0
  19. package/plugin/dashboard/components/common/learning-application-badge.tsx +34 -0
  20. package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
  21. package/plugin/dashboard/components/common/page-header.tsx +3 -3
  22. package/plugin/dashboard/lib/config-file.ts +5 -1
  23. package/plugin/dashboard/lib/host-attribution.ts +62 -0
  24. package/plugin/dashboard/lib/session-reader.ts +40 -2
  25. package/plugin/dashboard/lib/types.ts +7 -1
  26. package/plugin/opencode/dist/server.mjs +60 -2
  27. package/plugin/opencode/server.mts +64 -2
  28. package/plugin/pyproject.toml +2 -2
  29. package/plugin/scripts/_lib.sh +255 -7
  30. package/plugin/scripts/backend-python-runner.py +46 -0
  31. package/plugin/scripts/backend-service.sh +766 -123
  32. package/plugin/scripts/codex-hook.js +79 -229
  33. package/plugin/scripts/dashboard-open.sh +6 -4
  34. package/plugin/scripts/dashboard-service.sh +118 -137
  35. package/plugin/scripts/ensure-plugin-root.sh +106 -8
  36. package/plugin/scripts/hook_entry.sh +3 -0
  37. package/plugin/scripts/opencode-claude-compat.js +8 -1
  38. package/plugin/scripts/smart-install.sh +116 -21
  39. package/plugin/src/README.md +1 -1
  40. package/plugin/src/claude_smart/cli.py +93 -29
  41. package/plugin/src/claude_smart/context_inject.py +3 -0
  42. package/plugin/src/claude_smart/env_config.py +56 -11
  43. package/plugin/src/claude_smart/events/post_tool.py +2 -1
  44. package/plugin/src/claude_smart/events/session_end.py +2 -1
  45. package/plugin/src/claude_smart/events/stop.py +3 -0
  46. package/plugin/src/claude_smart/events/user_prompt.py +2 -1
  47. package/plugin/src/claude_smart/internal_call.py +5 -2
  48. package/plugin/src/claude_smart/optimizer_assistant.py +59 -13
  49. package/plugin/src/claude_smart/publish.py +59 -7
  50. package/plugin/src/claude_smart/reflexio_adapter.py +137 -14
  51. package/plugin/src/claude_smart/runtime.py +15 -6
  52. package/plugin/src/claude_smart/state.py +211 -52
  53. package/plugin/uv.lock +5 -5
  54. package/plugin/vendor/reflexio/.env.example +13 -0
  55. package/plugin/vendor/reflexio/reflexio/README.md +7 -3
  56. package/plugin/vendor/reflexio/reflexio/__init__.py +12 -0
  57. package/plugin/vendor/reflexio/reflexio/cli/README.md +3 -0
  58. package/plugin/vendor/reflexio/reflexio/client/client.py +166 -9
  59. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +10 -3
  60. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +28 -0
  61. package/plugin/vendor/reflexio/reflexio/lib/_search.py +41 -0
  62. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +195 -25
  63. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +7 -1
  64. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +2 -1
  65. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +63 -4
  66. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +1 -0
  67. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +8 -1
  68. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
  69. package/plugin/vendor/reflexio/reflexio/server/README.md +22 -4
  70. package/plugin/vendor/reflexio/reflexio/server/__init__.py +18 -8
  71. package/plugin/vendor/reflexio/reflexio/server/__main__.py +6 -0
  72. package/plugin/vendor/reflexio/reflexio/server/api.py +79 -5
  73. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +263 -3
  74. package/plugin/vendor/reflexio/reflexio/server/callback_executor.py +164 -0
  75. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +81 -81
  76. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +28 -0
  77. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +63 -6
  78. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
  79. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1 -0
  80. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +28 -0
  81. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +9 -1
  82. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedder_warmup.py +329 -0
  83. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +85 -10
  84. package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +199 -2
  85. package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +77 -9
  86. package/plugin/vendor/reflexio/reflexio/server/org_fanout.py +184 -0
  87. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +1 -1
  88. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.1.0.prompt.md +32 -0
  89. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +1 -1
  90. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.4.0.prompt.md +63 -0
  91. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +1 -1
  92. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v2.0.0.prompt.md +30 -0
  93. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_impact/v1.0.0.prompt.md +51 -0
  94. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_relevance/v1.0.0.prompt.md +39 -0
  95. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +1 -1
  96. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.1.0.prompt.md +43 -0
  97. package/plugin/vendor/reflexio/reflexio/server/routes/config.py +3 -3
  98. package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +122 -28
  99. package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +63 -2
  100. package/plugin/vendor/reflexio/reflexio/server/routes/system.py +22 -3
  101. package/plugin/vendor/reflexio/reflexio/server/scheduling.py +64 -3
  102. package/plugin/vendor/reflexio/reflexio/server/services/README.md +6 -4
  103. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +3 -2
  104. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +41 -0
  105. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py +554 -0
  106. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +35 -1
  107. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/runner.py +253 -101
  108. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/scheduler.py +5 -7
  109. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/service.py +27 -0
  110. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +11 -5
  111. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +4 -4
  112. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +6 -2
  113. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +255 -74
  114. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +72 -0
  115. package/plugin/vendor/reflexio/reflexio/server/services/deferred_learning_plan.py +270 -0
  116. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/__init__.py +13 -0
  117. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/scheduler.py +142 -0
  118. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +262 -0
  119. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/hero_state.py +2 -11
  120. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/rule_attribution.py +6 -2
  121. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +17 -13
  122. package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +18 -3
  123. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +12 -1
  124. package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +1 -1
  125. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +2 -1
  126. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +1 -3
  127. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +66 -0
  128. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +640 -80
  129. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +2 -0
  130. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +179 -99
  131. package/plugin/vendor/reflexio/reflexio/server/services/lineage/vector_backfill_sweep.py +139 -0
  132. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +66 -27
  133. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_trigger.py +177 -0
  134. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -2
  135. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +360 -49
  136. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/extractor.py +27 -30
  137. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +8 -1
  138. package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +137 -69
  139. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +20 -1
  140. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +13 -6
  141. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +40 -25
  142. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/consolidator.py +12 -39
  143. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +30 -35
  144. package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +166 -66
  145. package/plugin/vendor/reflexio/reflexio/server/services/reflection/service.py +457 -107
  146. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/session_dedup.py +127 -0
  147. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/temporal.py +104 -0
  148. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/dispatcher.py +139 -0
  149. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +16 -6
  150. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/worker.py +137 -0
  151. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +12 -0
  152. package/plugin/vendor/reflexio/reflexio/server/services/storage/lifecycle_filters.py +54 -0
  153. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +41 -3
  154. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +40 -2
  155. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
  156. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +190 -2
  157. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +11 -4
  158. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +28 -1
  159. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +414 -0
  160. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -5
  161. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +8 -3
  162. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +4 -0
  163. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +16 -5
  164. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +86 -2
  165. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +104 -42
  166. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +7 -1
  167. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +59 -7
  168. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +430 -5
  169. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +8 -3
  170. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +57 -12
  171. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +197 -12
  172. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +70 -11
  173. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +17 -0
  174. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_commit_scope.py +9 -0
  175. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +9 -2
  176. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +269 -0
  177. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +7 -0
  178. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +3 -1
  179. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +4 -0
  180. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +12 -1
  181. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/evaluation_state_keys.py +78 -0
  182. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +38 -0
  183. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +171 -0
  184. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +52 -1
  185. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +82 -1
  186. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +74 -2
  187. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/retrieved_learning_state.py +226 -0
  188. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +5 -6
  189. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +209 -29
  190. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +3 -0
  191. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -0
  192. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +33 -0
  193. package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
@@ -19,6 +19,9 @@ set of hooks so no observable behaviour changes:
19
19
  ``start`` is a logged no-op when a feature gate is off).
20
20
  - :meth:`_on_started` / :meth:`_on_stopped` emit each scheduler's own start/stop
21
21
  log line (through its own module logger, so output is byte-identical).
22
+ - ``leader_gate`` (optional): a fleet-coordination gate consulted before each
23
+ tick; ``None`` (the default and the OSS-local case) preserves today's
24
+ always-tick behavior byte-for-byte.
22
25
 
23
26
  A scheduler whose loop is materially different (e.g. a bounded-attempt retrier
24
27
  that waits *before* each attempt and exits on first success) may override
@@ -31,7 +34,31 @@ nothing from ``reflexio_ext``.
31
34
 
32
35
  from __future__ import annotations
33
36
 
37
+ import logging
34
38
  import threading
39
+ from typing import Protocol
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+ # How long a non-leader waits before re-checking the gate. Fixed (no interval
44
+ # memoization): a follower that becomes leader starts ticking within <=60s.
45
+ _FOLLOWER_POLL_SECONDS: float = 60.0
46
+
47
+
48
+ class LeaderGate(Protocol):
49
+ """Fleet-coordination gate consulted before each tick.
50
+
51
+ Implementations must NEVER raise from ``should_run`` — error handling
52
+ (including fail-open) lives inside the implementation (spec §4.1). The
53
+ scheduler base nonetheless defends against a contract violation: if
54
+ ``should_run`` raises anyway, the base logs the error and fails open
55
+ (runs the tick) rather than trusting the contract blindly and letting the
56
+ daemon thread die silently.
57
+ """
58
+
59
+ def should_run(self) -> bool:
60
+ """Return whether this instance should run the next tick."""
61
+ ...
35
62
 
36
63
 
37
64
  class ThreadedScheduler:
@@ -44,12 +71,17 @@ class ThreadedScheduler:
44
71
 
45
72
  Args:
46
73
  thread_name (str): OS thread name for the daemon (aids debugging / logs).
74
+ leader_gate (LeaderGate | None): Optional fleet-coordination gate
75
+ consulted before each tick. Defaults to None (always tick).
47
76
  """
48
77
 
49
- def __init__(self, *, thread_name: str) -> None:
78
+ def __init__(
79
+ self, *, thread_name: str, leader_gate: LeaderGate | None = None
80
+ ) -> None:
50
81
  self._thread_name = thread_name
51
82
  self._stop_event = threading.Event()
52
83
  self._thread: threading.Thread | None = None
84
+ self._leader_gate = leader_gate
53
85
 
54
86
  def start(self) -> None:
55
87
  """Start the daemon thread, unless it is gated off or already running.
@@ -125,8 +157,37 @@ class ThreadedScheduler:
125
157
  """
126
158
  raise NotImplementedError
127
159
 
160
+ def _elected_interval(self) -> float:
161
+ """Run one gated iteration; return the seconds to wait before the next.
162
+
163
+ ``None`` gate -> tick (today's behavior). Gate ``True`` -> tick.
164
+ Gate ``False`` -> skip and wait the fixed follower poll.
165
+
166
+ The gate contract says ``should_run`` never raises (see
167
+ :class:`LeaderGate`), but this has zero defense of its own: an escaped
168
+ exception here would kill the daemon thread permanently and silently.
169
+ A raise is therefore caught defensively and treated as fail-open (tick
170
+ runs; duplicate work is safe, silence is not) rather than propagated.
171
+
172
+ Returns:
173
+ float: Seconds to wait before the next loop iteration.
174
+ """
175
+ if self._leader_gate is None:
176
+ return self._run_once()
177
+ try:
178
+ should_run = self._leader_gate.should_run()
179
+ except Exception:
180
+ logger.exception(
181
+ "event=%s_leader_gate_error — failing open", self._thread_name
182
+ )
183
+ return self._run_once()
184
+ if should_run:
185
+ return self._run_once()
186
+ logger.debug("event=%s_skip_not_leader", self._thread_name)
187
+ return _FOLLOWER_POLL_SECONDS
188
+
128
189
  def _run_loop(self) -> None:
129
- """Drive :meth:`_run_once` until stopped, waiting its returned interval."""
190
+ """Drive gated iterations until stopped, waiting each returned interval."""
130
191
  while not self._stop_event.is_set():
131
- interval = self._run_once()
192
+ interval = self._elected_interval()
132
193
  self._stop_event.wait(interval)
@@ -22,7 +22,7 @@ strings before deleting old import paths in the same PR.
22
22
 
23
23
  | File | Purpose |
24
24
  |------|---------|
25
- | `generation_service.py` | `GenerationService` — saves interactions, runs profile + playbook generation in parallel (ThreadPoolExecutor), schedules deferred evaluation when `session_id` is present. |
25
+ | `generation_service.py` | `GenerationService` — saves interactions, runs/defer-runs profile + playbook generation, schedules deferred evaluation when `session_id` is present, exposes `run_deferred_learning()` for the non-durable publish worker, and the durable `compute_deferred_learning()` → `persist_deferred_learning()` → `emit_deferred_learning_side_effects()` split (compute outside any scope; only persist + fence inside `commit_scope()`). |
26
26
  | `publish_learning_worker.py` | Deferred post-persist publish learning worker — queues async publish learning after durable interaction writes and requeues under publish limiter pressure. |
27
27
  | `base_generation_service.py` + `base_generation/` | `BaseGenerationService` stable import surface plus mixins for batch progress, config filtering, extraction lifecycle, should-run prechecks, status transitions, and usage billing. Per-extractor timeout `EXTRACTOR_TIMEOUT_SECONDS = 300`. |
28
28
  | `operation_state_utils.py` | `OperationStateManager` — all `_operation_state` access (progress, concurrency locks, extractor/aggregator bookmarks, cluster fingerprints, cancellation). |
@@ -38,10 +38,11 @@ strings before deleting old import paths in the same PR.
38
38
  | `agent_success_evaluation/` | `AgentSuccessEvaluationService` | `service.py` (session-level service), `runner.py` (`run_group_evaluation`), `scheduler.py` (`GroupEvaluationScheduler`, 10-min defer), `regen_jobs.py`, `components/evaluator.py` |
39
39
  | `reflection/` | `ReflectionService` | `service.py`, `components/extractor.py` — post-horizon reflection; runs **before** extraction so extractors read post-reflection state |
40
40
 
41
- ## Async Extraction
41
+ ## Durable and Async Extraction
42
42
 
43
43
  | Directory | Purpose |
44
44
  |-----------|---------|
45
+ | `durable_learning/` | Durable `learning_jobs` scheduler + worker. Gated by `REFLEXIO_DURABLE_LEARNING_QUEUE`; discovers orgs with pending work, claims jobs with leases, then per job runs `compute_deferred_learning()` **outside** any writer transaction, applies `persist_deferred_learning()` + fenced `complete_learning_job()` inside a short `storage.commit_scope()`, and fires `emit_deferred_learning_side_effects()` post-commit — fenced completion guarantees exactly-once side effects. |
45
46
  | `extraction/` | Shared async extraction runtime: `resumable_agent.py`, `resume_scheduler.py`, `resume_worker.py`, `pending_tool_call_dispatch.py` (`ask_human`), `prior_answer_search.py`, `agent_run_records.py`, and `outcome.py`. Long-horizon / tool-mediated extraction continues outside the request path. See [README](extraction/README.md). |
46
47
 
47
48
  ## Evaluation, Search & Integrations
@@ -57,13 +58,13 @@ strings before deleting old import paths in the same PR.
57
58
  | `pre_retrieval/` | `QueryReformulator` (`_query_reformulator.py`) + `DocumentExpander` (`_document_expander.py`) - query rewrite and doc expansion for recall. Compact by design; see [README](pre_retrieval/README.md). |
58
59
  | `tagging/` | `TaggingService` (`service.py`) + deferred `tagging_scheduler.py` - post-generation profile/playbook tagging. Compact by design; see [README](tagging/README.md). |
59
60
  | `unified_search_service.py` | `run_unified_search()` — two-phase parallel search across profiles / agent playbooks / user playbooks. |
60
- | `retrieval/` | `relevance_floor.py` — result relevance thresholding. |
61
+ | `retrieval/` | `relevance_floor.py` — result relevance thresholding. `temporal.py` — temporal post-processing driven by reformulation signals: query time windows → per-arm SQL filters, near-duplicate freshness collapse for current-value questions, timestamp ordering for latest-value questions. (Superseded/expired rows are already excluded by storage search SQL.) |
61
62
 
62
63
  ## Persistence & Config
63
64
 
64
65
  | Path | Purpose |
65
66
  |------|---------|
66
- | `storage/` | `storage_base/` and `sqlite_storage/` keep legacy domain facades while focused subpackages own `profiles/`, `playbook/`, `agent_run/`, `governance/`, and SQLite `base/` helpers; plus `governance_validation.py` and `retention*.py`. Access via `request_context.storage` only. |
67
+ | `storage/` | `storage_base/` and `sqlite_storage/` keep legacy domain facades while focused subpackages own `profiles/`, `playbook/`, `agent_run/`, `governance/`, durable `learning_jobs`, and SQLite `base/` helpers; plus `governance_validation.py` and `retention*.py`. Access via `request_context.storage` only. |
67
68
  | `configurator/` | `DefaultConfigurator` — loads YAML config and creates the storage backend. |
68
69
 
69
70
  ## Key Rules
@@ -72,5 +73,6 @@ strings before deleting old import paths in the same PR.
72
73
  - **NEVER import storage implementations directly** — use `request_context.storage` (`BaseStorage`).
73
74
  - **ALWAYS use `LiteLLMClient`** for completions/embeddings and `request_context.prompt_manager.render_prompt(...)` for prompts — no hardcoded prompts, no direct OpenAI/Claude clients.
74
75
  - **All `_operation_state` writes go through `OperationStateManager`** — don't touch the table directly (it backs locks, bookmarks, progress, and cancellation).
76
+ - **All durable queue claims go through `LearningJobStoreABC`** — do not update `learning_jobs` directly; completion is fenced by `claim_token` and must roll back the surrounding `commit_scope` when superseded.
75
77
  - **`tool_can_use` lives at root `Config`** — shared by playbook extraction and success evaluation, not per-service.
76
78
  - **Preserve governance subject refs/barriers** — route validation through `services/governance/` and `storage/governance_validation.py`; do not bypass retention or subject-write checks in storage implementations.
@@ -5,9 +5,10 @@ Session-level agent success evaluation module.
5
5
  ## Module Shape
6
6
 
7
7
  - `service.py`: `AgentSuccessEvaluationService`, the request-path service that runs configured evaluators and saves result rows.
8
- - `runner.py`: `run_group_evaluation(...)`, the background/manual workflow entry point that loads a session, runs the service, dispatches shadow comparison, and marks operation state.
8
+ - `runner.py`: `run_group_evaluation(...)`, the background/manual workflow entry point that loads a session, runs the service, and marks operation state. After agent-success work it also runs the retrieved-learning evaluation (independent completion; generation + session-fingerprint fenced) and returns a `GroupEvaluationOutcome` carrying both statuses.
9
9
  - `scheduler.py`: `GroupEvaluationScheduler`, the deferred inactivity scheduler.
10
10
  - `components/evaluator.py`: `AgentSuccessEvaluator`, the LLM evaluator component.
11
+ - `components/retrieved_learning_evaluator.py`: `RetrievedLearningEvaluator`, per-learning relevance/impact judges over `Interaction.retrieved_learnings`; results go to the `retrieved_learning_evaluation` table.
11
12
  - `regen_jobs.py`: regeneration job planning and execution; remains root-level because API/admin regenerate flows import it directly.
12
13
  - `_eval_health.py`: producer/scheduler health counters.
13
14
  - `agent_success_evaluation_constants.py`: prompt/model output constants.
@@ -15,7 +16,7 @@ Session-level agent success evaluation module.
15
16
 
16
17
  ## Prompt IDs
17
18
 
18
- - Owns `agent_success_evaluation`.
19
+ - Owns `agent_success_evaluation`, `retrieved_learning_relevance`, and `retrieved_learning_impact`.
19
20
  - Keeps historical/configured prompt ID `agent_success_evaluation_with_comparison` stable where prompt mapping tests require it.
20
21
 
21
22
  Do not reintroduce the deleted service/evaluator/runner/scheduler legacy
@@ -51,6 +51,34 @@ class EvalHealth:
51
51
  self._skip_counts: Counter[SkipReason] = Counter()
52
52
  self._failures: deque[float] = deque()
53
53
  self._last_tick_monotonic: float | None = None
54
+ self._retrieved_outcome_counts: Counter[str] = Counter()
55
+ self._retrieved_invalid_ref_count = 0
56
+ self._retrieved_failed_relevance_chunks = 0
57
+ self._retrieved_failed_impact_chunks = 0
58
+
59
+ def record_retrieved_outcome(
60
+ self, status: str, *, diagnostics: dict[str, Any] | None = None
61
+ ) -> None:
62
+ """Record one retrieved-learning evaluation outcome.
63
+
64
+ Args:
65
+ status (str): Final invocation status (complete/degraded/failed/
66
+ not_applicable/...).
67
+ diagnostics (dict, optional): Evaluator diagnostics; the
68
+ invalid-ref and failed-chunk counters are accumulated from it.
69
+ """
70
+ with self._lock:
71
+ self._retrieved_outcome_counts[status] += 1
72
+ if diagnostics:
73
+ self._retrieved_invalid_ref_count += int(
74
+ diagnostics.get("invalid_ref_count") or 0
75
+ )
76
+ self._retrieved_failed_relevance_chunks += int(
77
+ diagnostics.get("failed_relevance_chunks") or 0
78
+ )
79
+ self._retrieved_failed_impact_chunks += int(
80
+ diagnostics.get("failed_impact_chunks") or 0
81
+ )
54
82
 
55
83
  def record_skip(self, reason: SkipReason) -> None:
56
84
  """Bump the counter for `reason` by one.
@@ -99,6 +127,12 @@ class EvalHealth:
99
127
  "skip_counts": {r.value: self._skip_counts[r] for r in SkipReason},
100
128
  "producer_failures_24h": len(self._failures),
101
129
  "last_tick_monotonic": self._last_tick_monotonic,
130
+ "retrieved_learning": {
131
+ "outcome_counts": dict(self._retrieved_outcome_counts),
132
+ "invalid_ref_count": self._retrieved_invalid_ref_count,
133
+ "failed_relevance_chunks": self._retrieved_failed_relevance_chunks,
134
+ "failed_impact_chunks": self._retrieved_failed_impact_chunks,
135
+ },
102
136
  }
103
137
 
104
138
  def _trim_locked(self, now_ts: float) -> None:
@@ -116,6 +150,13 @@ def record_skip(reason: SkipReason) -> None:
116
150
  _HEALTH.record_skip(reason)
117
151
 
118
152
 
153
+ def record_retrieved_outcome(
154
+ status: str, *, diagnostics: dict[str, Any] | None = None
155
+ ) -> None:
156
+ """Module-level proxy to the singleton."""
157
+ _HEALTH.record_retrieved_outcome(status, diagnostics=diagnostics)
158
+
159
+
119
160
  def record_producer_failure(at_ts: float | None = None) -> None:
120
161
  """Module-level proxy to the singleton."""
121
162
  _HEALTH.record_producer_failure(at_ts=at_ts)