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
@@ -11,6 +11,7 @@ from typing import Literal, Self
11
11
 
12
12
  from pydantic import BaseModel, ConfigDict, Field, model_validator
13
13
 
14
+ from reflexio.models.api_schema.domain import CitationKind
14
15
  from reflexio.models.api_schema.validators import NonEmptyStr
15
16
  from reflexio.models.structured_output import StrictStructuredOutput
16
17
 
@@ -79,7 +80,7 @@ class RuleAttributionRow(BaseModel):
79
80
  """One row in the "rules that moved the needle" panel."""
80
81
 
81
82
  rule_id: str
82
- kind: Literal["playbook", "profile"]
83
+ kind: CitationKind
83
84
  title: str = ""
84
85
  successes_with: int = Field(ge=0)
85
86
  failures_with: int = Field(ge=0)
@@ -416,12 +417,17 @@ class GradeOnDemandResponse(BaseModel):
416
417
  window. False on a fresh grade.
417
418
  skipped_reason (str | None): If grading was skipped, the reason
418
419
  (e.g., "NO_REQUESTS"). None on success.
420
+ retrieved_learning_status (str | None): Outcome of the
421
+ retrieved-learning evaluation for this session (e.g.
422
+ "complete", "degraded", "failed", "not_applicable"). None on
423
+ legacy cache entries and skipped grades.
419
424
  """
420
425
 
421
426
  session_id: str
422
427
  result_id: int | None = None
423
428
  cached: bool = False
424
429
  skipped_reason: str | None = None
430
+ retrieved_learning_status: str | None = None
425
431
 
426
432
 
427
433
  # ---------------------------------------------------------------------------
@@ -6,6 +6,7 @@ from typing import NamedTuple
6
6
 
7
7
  from pydantic import BaseModel
8
8
 
9
+ from .domain import CitationKind
9
10
  from .service_schemas import Interaction, Request
10
11
 
11
12
 
@@ -42,6 +43,6 @@ class SessionCitation(NamedTuple):
42
43
 
43
44
  user_id: str
44
45
  session_id: str
45
- kind: str
46
+ kind: CitationKind
46
47
  real_id: str
47
48
  title: str
@@ -6,12 +6,15 @@ from typing import Literal, Self
6
6
  from pydantic import BaseModel, Field, model_validator
7
7
 
8
8
  from ..config_schema import SearchMode
9
+ from ..structured_output import StrictStructuredOutput
10
+ from .domain import CitationKind
9
11
  from .service_schemas import (
10
12
  AgentPlaybook,
11
13
  AgentSuccessEvaluationResult,
12
14
  Interaction,
13
15
  PlaybookStatus,
14
16
  Request,
17
+ RetrievedLearningEvaluationResult,
15
18
  Status,
16
19
  UserPlaybook,
17
20
  UserProfile,
@@ -433,6 +436,26 @@ class GetAgentSuccessEvaluationResultsResponse(BaseModel):
433
436
  msg: str | None = None
434
437
 
435
438
 
439
+ class GetRetrievedLearningEvaluationResultsRequest(BaseModel):
440
+ """Read per-learning retrieved-learning evaluation verdicts.
441
+
442
+ Attributes:
443
+ user_id (str | None): Filter by session owner.
444
+ session_id (str | None): Filter by session.
445
+ limit (int): Maximum rows, ordered by created_at DESC, result_id DESC.
446
+ """
447
+
448
+ user_id: str | None = None
449
+ session_id: str | None = None
450
+ limit: int = Field(default=100, ge=1, le=1_000)
451
+
452
+
453
+ class GetRetrievedLearningEvaluationResultsResponse(BaseModel):
454
+ success: bool
455
+ results: list[RetrievedLearningEvaluationResult] = Field(default_factory=list)
456
+ msg: str | None = None
457
+
458
+
436
459
  class GetRequestsRequest(BaseModel):
437
460
  user_id: str | None = None
438
461
  request_id: str | None = None
@@ -593,8 +616,10 @@ class PlaybookApplicationStat(BaseModel):
593
616
  real_id (str): Stable id of the cited item — ``user_playbook_id``,
594
617
  ``agent_playbook_id``, or ``profile_id`` (always serialized as a
595
618
  string).
596
- kind (str): ``"playbook"`` or ``"profile"`` — the citation kind, as
597
- recorded on ``Interaction.citations``.
619
+ kind (CitationKind): Citation kind recorded on
620
+ ``Interaction.citations``. ``"playbook"`` is the legacy
621
+ compatibility value; ``"user_playbook"`` is the explicit
622
+ direct-tuner target.
598
623
  title (str): Human-readable label for the rule. Empty string when
599
624
  the underlying row has been deleted but old citations remain.
600
625
  applied_count (int): Number of interactions in the window whose
@@ -609,7 +634,7 @@ class PlaybookApplicationStat(BaseModel):
609
634
  """
610
635
 
611
636
  real_id: str
612
- kind: Literal["playbook", "profile"]
637
+ kind: CitationKind
613
638
  title: str = ""
614
639
  applied_count: int = Field(ge=0)
615
640
  last_applied_at: int | None = None
@@ -659,15 +684,36 @@ class ConversationTurn(BaseModel):
659
684
  content: NonEmptyStr
660
685
 
661
686
 
662
- class ReformulationResult(BaseModel):
687
+ class ReformulationResult(StrictStructuredOutput):
663
688
  """Output of the query reformulation pipeline.
664
689
 
690
+ Besides the rewritten query, carries the query's TEMPORAL SIGNALS so the
691
+ search pipeline can be time-sensitive without any additional LLM call
692
+ (the reformulation call already runs before retrieval when
693
+ ``enable_reformulation`` is set). Time windows are relative day offsets
694
+ (never absolute dates) so results don't rot with calendar time.
695
+
665
696
  Args:
666
697
  standalone_query (str): Clean, normalized natural language query with
667
698
  conversation context resolved, abbreviations expanded, grammar fixed.
699
+ start_days_ago (float, optional): Older bound of a query time window
700
+ ("in the last 7 days" → 7).
701
+ end_days_ago (float, optional): Newer bound ("before this month" →
702
+ ~30, with no start bound).
703
+ recency_dominant (bool): The query asks for the CURRENT/LATEST value —
704
+ final ordering becomes timestamp-based.
705
+ wants_current (bool): Present-tense question about a mutable
706
+ fact/policy — near-duplicate competing facts collapse to the
707
+ freshest, while relevance ordering is otherwise preserved.
708
+ (Superseded/TTL-expired rows never reach results: storage search
709
+ excludes tombstone statuses and expired profiles at SQL level.)
668
710
  """
669
711
 
670
712
  standalone_query: str
713
+ start_days_ago: float | None = Field(default=None, ge=0)
714
+ end_days_ago: float | None = Field(default=None, ge=0)
715
+ recency_dominant: bool = False
716
+ wants_current: bool = False
671
717
 
672
718
 
673
719
  # ===============================
@@ -712,6 +758,9 @@ class UnifiedSearchRequest(BaseModel):
712
758
  search_mode: SearchMode = SearchMode.HYBRID
713
759
  # Caller correlation IDs for billing attribution on the Application line.
714
760
  # Optional; consumed by _meter_applied_learnings in server/api.py.
761
+ # ``session_id`` additionally enables session-scoped result dedup: items
762
+ # already served to the same (org, session) are skipped and the next-best
763
+ # matches backfilled (see server/services/retrieval/session_dedup.py).
715
764
  request_id: str | None = None
716
765
  session_id: str | None = None
717
766
  interaction_id: int | None = Field(default=None, gt=0)
@@ -729,6 +778,14 @@ class UnifiedSearchResponse(BaseModel):
729
778
  msg (str, optional): Additional message
730
779
  agent_answer (str, optional): LLM-synthesised answer populated by the agentic backend;
731
780
  None for classic backend.
781
+ degraded (bool): True when the search silently fell back from the
782
+ requested vector/hybrid mode to full-text search because query
783
+ embedding generation failed. Results are still returned (via FTS),
784
+ but relevance may be lower than a healthy vector/hybrid run.
785
+ Defaults to False.
786
+ search_mode_effective (str, optional): The search mode actually used
787
+ when it differs from the requested mode — currently ``"fts"`` on
788
+ the degrade path. None when the requested mode was honored.
732
789
  """
733
790
 
734
791
  success: bool
@@ -740,6 +797,8 @@ class UnifiedSearchResponse(BaseModel):
740
797
  agent_answer: str | None = None
741
798
  agent_trace: str | None = None
742
799
  rehydrated_text: str | None = None
800
+ degraded: bool = False
801
+ search_mode_effective: str | None = None
743
802
 
744
803
 
745
804
  # ===============================
@@ -53,6 +53,7 @@ def to_interaction_view(interaction: Interaction) -> InteractionView:
53
53
  shadow_content=interaction.shadow_content,
54
54
  expert_content=interaction.expert_content,
55
55
  tools_used=interaction.tools_used,
56
+ retrieved_learnings=interaction.retrieved_learnings,
56
57
  )
57
58
 
58
59
 
@@ -5,6 +5,7 @@ from datetime import UTC, datetime
5
5
  from pydantic import BaseModel, Field, field_validator
6
6
 
7
7
  from ..common import NEVER_EXPIRES_TIMESTAMP, ToolUsed
8
+ from ..domain.entities import RetrievedLearning
8
9
  from ..validators import _validate_image_url
9
10
  from .enums import (
10
11
  PlaybookStatus,
@@ -29,7 +30,12 @@ __all__ = [
29
30
 
30
31
 
31
32
  class InteractionView(BaseModel):
32
- """User-facing Interaction — excludes embedding and image_encoding."""
33
+ """User-facing Interaction — excludes embedding and image_encoding.
34
+
35
+ ``retrieved_learnings`` (caller-supplied at publish) is exposed so callers
36
+ can round-trip what they attached; ``citations`` (agent-claimed influence,
37
+ server-side signal) is deliberately NOT exposed on this view.
38
+ """
33
39
 
34
40
  interaction_id: int = 0
35
41
  user_id: str
@@ -43,6 +49,7 @@ class InteractionView(BaseModel):
43
49
  shadow_content: str = ""
44
50
  expert_content: str = ""
45
51
  tools_used: list[ToolUsed] = Field(default_factory=list)
52
+ retrieved_learnings: list[RetrievedLearning] = Field(default_factory=list)
46
53
 
47
54
  @field_validator("interacted_image_url", mode="after")
48
55
  @classmethod
@@ -914,7 +914,7 @@ class Config(BaseModel):
914
914
  ),
915
915
  )
916
916
  shadow_comparison_judge_prompt_version: NonEmptyStr = Field(
917
- default="v1.0.0",
917
+ default="v1.1.0",
918
918
  description=(
919
919
  "F1: pinned judge prompt version for per-turn shadow comparison. "
920
920
  "Verdicts are stored with the version that produced them; the "
@@ -17,6 +17,7 @@ Description: FastAPI backend server that processes user interactions to generate
17
17
  - [Profile Generation](#profile-generation)
18
18
  - [Playbook Extraction](#playbook-extraction)
19
19
  - [Agent Success Evaluation](#agent-success-evaluation)
20
+ - [Durable Learning Queue](#durable-learning-queue)
20
21
  - [Reflection and Async Extraction](#reflection-and-async-extraction)
21
22
  - [Shadow Comparison and Evaluation Overview](#shadow-comparison-and-evaluation-overview)
22
23
  - [Playbook Optimizer and Braintrust](#playbook-optimizer-and-braintrust)
@@ -38,6 +39,7 @@ Description: FastAPI backend server that processes user interactions to generate
38
39
  - **Endpoint Helpers**: `api_endpoints/` - Shared handlers/helpers plus `RequestContext` used by route modules
39
40
  - **Extension Registry**: `extensions.py` - Capability and service registry for optional OSS/enterprise integrations
40
41
  - **Core Service**: `services/generation_service.py` - Main orchestrator
42
+ - **Durable Learning**: `services/durable_learning/` - background queue worker for deferred post-publish extraction
41
43
 
42
44
  ## Cache
43
45
 
@@ -86,7 +88,7 @@ Description: FastAPI backend server that processes user interactions to generate
86
88
  - **Health/version**: `GET /`, `GET /health`, `GET /healthz`, `GET /healthz/eval`, `GET /meta/version`
87
89
  - **Identity/config**: `GET /api/whoami`, `GET /api/my_config`, `GET /api/get_config`, `POST /api/set_config`, `POST /api/update_config`
88
90
  - **Publish/direct writes**: `POST /api/publish_interaction`, `POST /api/add_user_profile`, `POST /api/add_user_playbook`, `POST /api/add_agent_playbook`
89
- - **Retrieval**: `POST /api/get_requests`, `POST /api/get_interactions`, `GET /api/get_all_interactions`, `POST /api/get_profiles`, `GET /api/get_all_profiles`, `POST /api/get_user_playbooks`, `POST /api/get_agent_playbooks`, `POST /api/get_agent_success_evaluation_results`
91
+ - **Retrieval**: `POST /api/get_requests`, `POST /api/get_interactions`, `GET /api/get_all_interactions`, `GET /api/learning_status`, `POST /api/get_profiles`, `GET /api/get_all_profiles`, `POST /api/get_user_playbooks`, `POST /api/get_agent_playbooks`, `POST /api/get_agent_success_evaluation_results`, `POST /api/get_retrieved_learning_evaluation_results`
90
92
  - **Search/stats**: `POST /api/search`, `POST /api/search_profiles`, `POST /api/rerank_user_profiles`, `POST /api/search_interactions`, `POST /api/search_user_playbooks`, `POST /api/search_agent_playbooks`, `GET /api/storage_stats`, `GET /api/get_profile_statistics`, `POST /api/get_dashboard_stats`, `POST /api/get_playbook_application_stats`
91
93
  - **Profile lifecycle**: `POST /api/rerun_profile_generation`, `POST /api/manual_profile_generation`, `POST /api/upgrade_all_profiles`, `POST /api/downgrade_all_profiles`, `GET /api/profile_change_log`, `PUT /api/update_user_profile`, `DELETE /api/delete_profile`, `DELETE /api/delete_profiles_by_ids`, `DELETE /api/delete_all_profiles`
92
94
  - **Playbook lifecycle**: `POST /api/rerun_playbook_generation`, `POST /api/manual_playbook_generation`, `POST /api/run_playbook_aggregation`, `GET /api/playbook_aggregation_change_logs`, `POST /api/upgrade_all_user_playbooks`, `POST /api/downgrade_all_user_playbooks`, `PUT /api/update_agent_playbook_status`, `PUT /api/update_agent_playbook`, `PUT /api/update_user_playbook`, `DELETE /api/delete_agent_playbook`, `DELETE /api/delete_user_playbook`, `DELETE /api/delete_agent_playbooks_by_ids`, `DELETE /api/delete_user_playbooks_by_ids`, `DELETE /api/delete_all_playbooks`, `DELETE /api/delete_all_user_playbooks`, `DELETE /api/delete_all_agent_playbooks`
@@ -210,12 +212,14 @@ python -m reflexio.server.scripts.manage_invitation_codes list --show-used
210
212
  - **Profile memory**: `profile/` extracts, deduplicates, and applies user profile updates.
211
213
  - **Playbook memory**: `playbook/` extracts user playbooks, consolidates them against existing rows, aggregates them into agent playbooks, and tracks aggregation change logs.
212
214
  - **Evaluation**: `agent_success_evaluation/service.py`, `agent_success_evaluation/runner.py`, `agent_success_evaluation/scheduler.py`, `agent_success_evaluation/components/evaluator.py`, `shadow_comparison/`, and `evaluation_overview/` handle session grading, per-turn shadow verdicts, regeneration jobs, and dashboard-facing rollups.
215
+ - **Durable learning queue**: `durable_learning/scheduler.py` and `durable_learning/worker.py` drain `learning_jobs` after deferred publishes and report coverage through `GET /api/learning_status`.
213
216
  - **Async clarification**: `extraction/` and `reflection/` manage resumable agent runs, pending tool calls, prior-answer search, and long-horizon reflection updates.
214
217
  - **Search preparation**: `pre_retrieval/` and `unified_search_service.py` handle query reformulation, document expansion, embeddings, and cross-entity search orchestration.
215
218
  - **Optimization/integrations**: `playbook_optimizer/` and `braintrust/` run candidate playbook optimization, rollout support, and Braintrust export/sync.
216
219
  - **Lineage**: `lineage/` resolves active records across superseded chains and schedules tombstone garbage collection for profile/playbook storage.
217
220
  - **Governance**: `governance/` defines subject-reference contracts and retention/barrier policy helpers used by storage and lineage paths.
218
221
  - **Persistence/config**: `storage/`, `configurator/`, and `operation_state_utils.py` provide storage abstractions, config loading, locks, bookmarks, progress, and cancellation.
222
+ - **Usage metering**: `billing_meter.py` converts learning/search signals into optional `usage_events` without importing enterprise types.
219
223
 
220
224
  ### Orchestrator
221
225
 
@@ -415,7 +419,9 @@ Key files:
415
419
  - `agent_success_evaluation_constants.py`: Output schema (`AgentSuccessEvaluationOutput`)
416
420
  - `agent_success_evaluation_utils.py`: Message construction utilities
417
421
  - `scheduler.py`: `GroupEvaluationScheduler` singleton - min-heap priority queue with daemon thread, defers evaluation until 10 min after last request in session
418
- - `runner.py`: `run_group_evaluation()` - fetches all requests/interactions for a session, builds `RequestInteractionDataModel` list, runs `service.py`
422
+ - `runner.py`: `run_group_evaluation()` - fetches all requests/interactions for a session, builds `RequestInteractionDataModel` list, runs `service.py`, then the retrieved-learning phase; returns `GroupEvaluationOutcome` (per-family statuses)
423
+ - `components/retrieved_learning_evaluator.py`: `RetrievedLearningEvaluator` - per-learning relevance/impact judges over `Interaction.retrieved_learnings`; results replace the session's `retrieved_learning_evaluation` snapshot atomically (generation + session-fingerprint fenced, see `services/storage/storage_base/retrieved_learning_state.py`)
424
+ - `services/storage/storage_base/evaluation_state_keys.py`: single source of truth for the three evaluation `_operation_state` key formats (agent-success marker, grade-on-demand cache, retrieved-learning state) shared by producers and governance erasure
419
425
 
420
426
  **Flow**: Interactions → `agent_success_evaluation/scheduler.py` → `agent_success_evaluation/runner.py` → `agent_success_evaluation/service.py` → `agent_success_evaluation/components/evaluator.py` → `AgentSuccessEvaluationResult` → Storage
421
427
 
@@ -423,7 +429,18 @@ Key files:
423
429
 
424
430
  **Tool Context**: Reads `tool_can_use` from root `Config` level (shared with playbook extraction).
425
431
 
426
- **Shadow Comparison**: Session-level shadow comparison was retracted in F1 because multi-turn shadow content suffers from trajectory contamination (turn 2+ user messages react to the regular response, not the shadow). The `regular_vs_shadow` field on `AgentSuccessEvaluationResult` is preserved as a nullable historical column but is always `None` on newly produced rows. Per-turn shadow comparison lives in a dedicated `services/shadow_comparison/` judge that writes its verdicts to a separate table see the F1 spec.
432
+ **Shadow Comparison**: Session-level shadow comparison was retracted in F1 because multi-turn shadow content suffers from trajectory contamination (turn 2+ user messages react to the regular response, not the shadow). The `regular_vs_shadow` field on `AgentSuccessEvaluationResult` is preserved as a nullable historical column but is always `None` on newly produced rows. Per-turn shadow comparison is scheduled from the publish path whenever an assistant interaction carries `shadow_content`; it lives in a dedicated `services/shadow_comparison/` judge that writes verdicts to a separate table, independent of session-level evaluation sampling.
433
+
434
+ ### Durable Learning Queue
435
+
436
+ **Directory**: `services/durable_learning/`
437
+
438
+ Key files:
439
+ - `scheduler.py`: `DurableLearningScheduler` plus `maybe_start_durable_learning()`; starts only when `REFLEXIO_DURABLE_LEARNING_QUEUE` is truthy and polls orgs with actionable queue rows.
440
+ - `worker.py`: `DurableLearningWorker`; claims leased jobs, reloads the persisted request, then splits each job into `compute_deferred_learning()` (LLM extraction + dedup + embeddings, **no** writer transaction held) → `persist_deferred_learning()` + fenced `complete_learning_job()` inside one short `storage.commit_scope()` → `emit_deferred_learning_side_effects()` post-commit (billing / telemetry / tagging / lock release).
441
+ - `services/storage/storage_base/_learning_jobs.py`: `LearningJobStoreABC`, queue status types, coverage-based request status, and the direct-storage contract implemented by each backend.
442
+
443
+ **Pattern**: `POST /api/publish_interaction` returns immediately when `wait_for_response=false`; callers use the returned `request_id` with `GET /api/learning_status`. Queue workers run the LLM compute **outside** any writer transaction; only the persist half + the fenced `complete_learning_job()` run inside `storage.commit_scope()`, and must raise/rollback if `complete_learning_job()` returns 0 because another worker stole the lease.
427
444
 
428
445
  ### Reflection and Async Extraction
429
446
 
@@ -444,7 +461,8 @@ Key files:
444
461
  **Directories**: `services/shadow_comparison/`, `services/evaluation_overview/`
445
462
 
446
463
  Key files:
447
- - `shadow_comparison/judge.py`: Per-turn regular-vs-shadow judge that writes shadow verdicts through storage
464
+ - `shadow_comparison/judge.py`: Per-turn regular-vs-shadow judge
465
+ - `shadow_comparison/dispatcher.py` and `shadow_comparison/worker.py`: Publish-time dispatch and bounded background execution for shadow verdict writes
448
466
  - `shadow_comparison/outcome.py`: Verdict outcome model helpers
449
467
  - `evaluation_overview/service.py`: Aggregates evaluation-page metrics
450
468
  - `evaluation_overview/components/hero_state.py`, `evaluation_overview/components/distribution.py`, `evaluation_overview/components/rule_attribution.py`, `evaluation_overview/components/shadow_aggregation.py`: Focused aggregation helpers
@@ -230,13 +230,16 @@ if DEBUG_LOG_TO_CONSOLE:
230
230
  logging.ERROR
231
231
  )
232
232
  else:
233
- # Production (DEBUG_LOG_TO_CONSOLE off): emit INFO+ app logs to stdout so the
234
- # container log driver (e.g. CloudWatch awslogs) captures business-logic audit
235
- # lines billing/webhook/enforcement, etc. Without an explicit handler the
236
- # stdlib "handler of last resort" emits only WARNING+ to stderr, so INFO logs
237
- # were silently dropped in production. Plain formatter (no colors/files/dedup)
238
- # keeps this lean and log-driver friendly none of the DEBUG-branch overhead.
239
- root_logger.setLevel(logging.INFO)
233
+ # Production (DEBUG_LOG_TO_CONSOLE off): default the root logger to WARNING.
234
+ # App-wide INFO on a busy server drove ~3x log volume and grew memory to the
235
+ # container ceiling over a few hours (prod incident 2026-07-04) — so INFO is
236
+ # OPT-IN per subsystem, not global. A stdout StreamHandler is still attached at
237
+ # INFO so that any logger explicitly raised to INFO reaches the container log
238
+ # driver (e.g. CloudWatch awslogs); everything else stays WARNING+ and cheap.
239
+ #
240
+ # REFLEXIO_INFO_LOGGERS: comma-separated logger-name prefixes to surface at INFO
241
+ # (e.g. the billing money-path). Empty/unset => WARNING-only (the safe default).
242
+ root_logger.setLevel(logging.WARNING)
240
243
  if not any(isinstance(h, logging.StreamHandler) for h in root_logger.handlers):
241
244
  prod_console_handler = logging.StreamHandler(sys.stdout)
242
245
  prod_console_handler.setLevel(logging.INFO)
@@ -245,7 +248,14 @@ else:
245
248
  )
246
249
  root_logger.addHandler(prod_console_handler)
247
250
 
248
- # Keep noisy loggers quiet even at INFO (mirror the debug branch's suppression).
251
+ for _info_logger in (
252
+ name.strip()
253
+ for name in os.getenv("REFLEXIO_INFO_LOGGERS", "").split(",")
254
+ if name.strip()
255
+ ):
256
+ logging.getLogger(_info_logger).setLevel(logging.INFO)
257
+
258
+ # Keep noisy loggers quiet regardless of the above (mirror the debug branch).
249
259
  for _noisy in ("litellm", "LiteLLM", "httpx", "httpcore", "openai", "urllib3"):
250
260
  logging.getLogger(_noisy).setLevel(logging.WARNING)
251
261
  logging.getLogger("reflexio.server.site_var.site_var_manager").setLevel(
@@ -15,6 +15,7 @@ Flags mirror the subset of ``uvicorn`` CLI options that
15
15
  from __future__ import annotations
16
16
 
17
17
  import argparse
18
+ import os
18
19
  import sys
19
20
 
20
21
  import uvicorn
@@ -100,6 +101,11 @@ def main(argv: list[str] | None = None) -> None:
100
101
  )
101
102
  raise SystemExit(2)
102
103
 
104
+ # Record the configured worker count so per-worker startup guards can read
105
+ # it (uvicorn exposes no worker-count env inside a worker process). See
106
+ # ``embedder_warmup._detected_worker_count``.
107
+ os.environ["REFLEXIO_SERVER_WORKERS"] = str(1 if args.reload else args.workers)
108
+
103
109
  if args.reload:
104
110
  uvicorn.run(
105
111
  args.app,
@@ -16,7 +16,7 @@ iterates (e.g. the QPS billable-endpoint scan over ``core_router.routes``).
16
16
 
17
17
  import inspect
18
18
  import logging
19
- from collections.abc import Callable
19
+ from collections.abc import Callable, Iterable
20
20
  from typing import TYPE_CHECKING
21
21
 
22
22
  if TYPE_CHECKING:
@@ -77,6 +77,34 @@ __all__ = [
77
77
  "limiter",
78
78
  ]
79
79
 
80
+
81
+ def _log_multi_worker_daemons() -> None:
82
+ """Warn once at startup when multiple worker processes will run daemons.
83
+
84
+ ``--workers N`` (N>1) runs the full daemon set in every worker process
85
+ (duplicate ticking). Safe by the concurrent-tick invariant — every daemon
86
+ tick is concurrent-safe — but worth one visible line (design D3).
87
+
88
+ Reuses ``embedder_warmup._detected_worker_count`` (checks
89
+ ``REFLEXIO_SERVER_WORKERS`` then falls back to ``WEB_CONCURRENCY``)
90
+ instead of re-implementing a narrower env read here. When the count is
91
+ undetectable (``None``), this logs nothing — it cannot verify the count,
92
+ so it must not assume a single worker.
93
+ """
94
+ from reflexio.server.llm.providers.embedder_warmup import (
95
+ _detected_worker_count,
96
+ )
97
+
98
+ workers = _detected_worker_count()
99
+ if workers is not None and workers > 1:
100
+ logger.warning(
101
+ "event=multi_worker_daemons workers=%d — background daemons tick in "
102
+ "every worker process (duplicate ticking; safe: ticks are "
103
+ "concurrent-safe by design)",
104
+ workers,
105
+ )
106
+
107
+
80
108
  # ``core_router`` stays an aggregator: it ``include_router``s every domain
81
109
  # sub-router so ``core_router.routes`` still enumerates all data-plane handlers
82
110
  # (enterprise QPS enforcement iterates this list). The include order is
@@ -269,6 +297,7 @@ def create_app(
269
297
  capabilities: "CapabilityRegistry | None" = None,
270
298
  app_context_factory: "Callable[[], AppContext] | None" = None,
271
299
  profile: "DeploymentProfile | None" = None,
300
+ durable_org_ids_provider: Callable[[], Iterable[str]] | None = None,
272
301
  ) -> FastAPI:
273
302
  """Factory to create a FastAPI app.
274
303
 
@@ -310,6 +339,16 @@ def create_app(
310
339
  provided it is the single source for router-group mounting, auth, and
311
340
  data-plane lifespan gating (the legacy knobs still populate the derived
312
341
  profile, so callers may pass either — they express the same thing).
342
+ durable_org_ids_provider: Optional zero-arg callable returning the org_ids
343
+ with actionable durable-learning work, threaded straight through to
344
+ ``maybe_start_durable_learning(org_ids_provider=...)``. When None
345
+ (default) the single-ref bootstrap default is used, so behaviour is
346
+ byte-identical to before this parameter existed. It is a plain
347
+ injectable seam so a deployment (e.g. enterprise cross-ref fan-out) can
348
+ supply its own discovery WITHOUT this factory importing deployment
349
+ logic. It is only consulted when ``REFLEXIO_DURABLE_LEARNING_QUEUE`` is
350
+ on — when the flag is off ``maybe_start_durable_learning`` returns None
351
+ without ever calling the provider.
313
352
 
314
353
  Returns:
315
354
  Configured FastAPI application.
@@ -345,19 +384,40 @@ def create_app(
345
384
  )
346
385
  from reflexio.server.extensions import AppContext
347
386
  from reflexio.server.llm.model_defaults import validate_llm_availability
387
+ from reflexio.server.services.durable_learning import (
388
+ maybe_start_durable_learning,
389
+ )
348
390
  from reflexio.server.services.extraction.resume_scheduler import (
349
391
  maybe_start_resume_scheduler,
350
392
  )
351
393
  from reflexio.server.services.lineage.gc_scheduler import (
352
394
  maybe_start_lineage_gc,
353
395
  )
396
+ from reflexio.server.services.lineage.vector_backfill_sweep import (
397
+ install_missing_vector_backfill_sweep,
398
+ )
354
399
 
355
400
  @asynccontextmanager
356
401
  async def lifespan(app: FastAPI) -> AsyncIterator[None]: # noqa: ARG001
357
402
  scheduler = None
358
403
  gc_scheduler = None
404
+ durable_learning_scheduler = None
359
405
  started_caps: list = []
406
+ # D8 config guards + D5 warm-before-ready. Both are dormant unless the
407
+ # deployment has flipped to the in-process local embedder
408
+ # (REFLEXIO_EMBEDDING_PROVIDER=inprocess + local/* default); pre-flip
409
+ # this is a no-op and /health stays byte-for-byte unchanged. Run before
410
+ # the data-plane block so a gated deployment warms regardless of the
411
+ # mount profile (otherwise /health could 503 forever).
412
+ from reflexio.server.llm.providers.embedder_warmup import (
413
+ maybe_start_embedder_warmup,
414
+ run_startup_config_guards,
415
+ )
416
+
417
+ run_startup_config_guards()
418
+ maybe_start_embedder_warmup()
360
419
  if mounts_data_plane:
420
+ _log_multi_worker_daemons()
361
421
  log_publish_hardware_capacity()
362
422
  validate_llm_availability()
363
423
  from reflexio.server.llm.rerank import prewarm as _prewarm_cross_encoder
@@ -372,10 +432,25 @@ def create_app(
372
432
  lambda org_id: RequestContext(org_id=org_id),
373
433
  bootstrap_org_id=bootstrap_org_id,
374
434
  )
435
+ # Register the missing-vector backfill sweep (opt-in via
436
+ # REFLEXIO_MISSING_VECTOR_BACKFILL_ENABLED) BEFORE starting the GC
437
+ # scheduler, so its per-org hook is visible when maybe_start_lineage_gc
438
+ # evaluates its start conditions. No-op when the flag is off.
439
+ install_missing_vector_backfill_sweep()
375
440
  gc_scheduler = maybe_start_lineage_gc(
376
441
  lambda org_id: RequestContext(org_id=org_id),
377
442
  bootstrap_org_id=bootstrap_org_id,
378
443
  )
444
+ # Durable learning drains the learning_jobs queue per org. Gated on
445
+ # REFLEXIO_DURABLE_LEARNING_QUEUE; the default provider discovers
446
+ # orgs-with-work via the bootstrap storage (single-ref). A deployment
447
+ # may inject its own discovery via ``durable_org_ids_provider`` (e.g.
448
+ # enterprise cross-ref fan-out); when None the single-ref default runs.
449
+ durable_learning_scheduler = maybe_start_durable_learning(
450
+ lambda org_id: RequestContext(org_id=org_id),
451
+ bootstrap_org_id=bootstrap_org_id,
452
+ org_ids_provider=durable_org_ids_provider,
453
+ )
379
454
  try:
380
455
  if capabilities is not None:
381
456
  ctx = (
@@ -397,10 +472,9 @@ def create_app(
397
472
  cap,
398
473
  exc_info=True,
399
474
  )
400
- if scheduler is not None:
401
- scheduler.stop()
402
- if gc_scheduler is not None:
403
- gc_scheduler.stop()
475
+ for sched in (scheduler, gc_scheduler, durable_learning_scheduler):
476
+ if sched is not None:
477
+ sched.stop()
404
478
  from reflexio.server.services.publish_learning_worker import (
405
479
  stop_publish_learning_worker,
406
480
  )