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.
- package/.claude-plugin/marketplace.json +2 -2
- package/README.md +11 -40
- package/bin/claude-smart.js +393 -55
- package/package.json +3 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.coverage +0 -0
- package/plugin/README.md +4 -3
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +4 -2
- package/plugin/dashboard/app/dashboard/page.tsx +6 -1
- package/plugin/dashboard/app/preferences/[id]/page.tsx +18 -6
- package/plugin/dashboard/app/preferences/page.tsx +32 -35
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +16 -1
- package/plugin/dashboard/app/sessions/page.tsx +2 -0
- package/plugin/dashboard/app/skills/page.tsx +65 -50
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +17 -8
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +1 -6
- package/plugin/dashboard/components/common/host-badge.tsx +118 -0
- package/plugin/dashboard/components/common/learning-application-badge.tsx +34 -0
- package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
- package/plugin/dashboard/components/common/page-header.tsx +3 -3
- package/plugin/dashboard/lib/config-file.ts +5 -1
- package/plugin/dashboard/lib/host-attribution.ts +62 -0
- package/plugin/dashboard/lib/session-reader.ts +40 -2
- package/plugin/dashboard/lib/types.ts +7 -1
- package/plugin/opencode/dist/server.mjs +60 -2
- package/plugin/opencode/server.mts +64 -2
- package/plugin/pyproject.toml +2 -2
- package/plugin/scripts/_lib.sh +255 -7
- package/plugin/scripts/backend-python-runner.py +46 -0
- package/plugin/scripts/backend-service.sh +766 -123
- package/plugin/scripts/codex-hook.js +79 -229
- package/plugin/scripts/dashboard-open.sh +6 -4
- package/plugin/scripts/dashboard-service.sh +118 -137
- package/plugin/scripts/ensure-plugin-root.sh +106 -8
- package/plugin/scripts/hook_entry.sh +3 -0
- package/plugin/scripts/opencode-claude-compat.js +8 -1
- package/plugin/scripts/smart-install.sh +116 -21
- package/plugin/src/README.md +1 -1
- package/plugin/src/claude_smart/cli.py +93 -29
- package/plugin/src/claude_smart/context_inject.py +3 -0
- package/plugin/src/claude_smart/env_config.py +56 -11
- package/plugin/src/claude_smart/events/post_tool.py +2 -1
- package/plugin/src/claude_smart/events/session_end.py +2 -1
- package/plugin/src/claude_smart/events/stop.py +3 -0
- package/plugin/src/claude_smart/events/user_prompt.py +2 -1
- package/plugin/src/claude_smart/internal_call.py +5 -2
- package/plugin/src/claude_smart/optimizer_assistant.py +59 -13
- package/plugin/src/claude_smart/publish.py +59 -7
- package/plugin/src/claude_smart/reflexio_adapter.py +137 -14
- package/plugin/src/claude_smart/runtime.py +15 -6
- package/plugin/src/claude_smart/state.py +211 -52
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/.env.example +13 -0
- package/plugin/vendor/reflexio/reflexio/README.md +7 -3
- package/plugin/vendor/reflexio/reflexio/__init__.py +12 -0
- package/plugin/vendor/reflexio/reflexio/cli/README.md +3 -0
- package/plugin/vendor/reflexio/reflexio/client/client.py +166 -9
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +10 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +28 -0
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +41 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +195 -25
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +7 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +2 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +63 -4
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +8 -1
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/README.md +22 -4
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +18 -8
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/api.py +79 -5
- package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +263 -3
- package/plugin/vendor/reflexio/reflexio/server/callback_executor.py +164 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +81 -81
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +63 -6
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedder_warmup.py +329 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +85 -10
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +199 -2
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +77 -9
- package/plugin/vendor/reflexio/reflexio/server/org_fanout.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.1.0.prompt.md +32 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.4.0.prompt.md +63 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v2.0.0.prompt.md +30 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_impact/v1.0.0.prompt.md +51 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_relevance/v1.0.0.prompt.md +39 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.1.0.prompt.md +43 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +3 -3
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +122 -28
- package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +63 -2
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +22 -3
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +64 -3
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +6 -4
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +41 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py +554 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +35 -1
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/runner.py +253 -101
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/scheduler.py +5 -7
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/service.py +27 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +11 -5
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +6 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +255 -74
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +72 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deferred_learning_plan.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/scheduler.py +142 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +262 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/hero_state.py +2 -11
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/rule_attribution.py +6 -2
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +17 -13
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +18 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +2 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +1 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +66 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +640 -80
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +179 -99
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/vector_backfill_sweep.py +139 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +66 -27
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_trigger.py +177 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -2
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +360 -49
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/extractor.py +27 -30
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +137 -69
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +20 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +13 -6
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +40 -25
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/consolidator.py +12 -39
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +30 -35
- package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +166 -66
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/service.py +457 -107
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/session_dedup.py +127 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/temporal.py +104 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/dispatcher.py +139 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +16 -6
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/worker.py +137 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +12 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/lifecycle_filters.py +54 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +41 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +40 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +190 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +11 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +28 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +414 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +8 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +16 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +86 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +104 -42
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +7 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +59 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +430 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +8 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +57 -12
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +197 -12
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +70 -11
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_commit_scope.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +9 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +269 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +3 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/evaluation_state_keys.py +78 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +171 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +52 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +82 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +74 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/retrieved_learning_state.py +226 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +5 -6
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +209 -29
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +3 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +33 -0
- 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:
|
|
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:
|
|
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 (
|
|
597
|
-
|
|
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:
|
|
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(
|
|
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.
|
|
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
|
|
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
|
|
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):
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
|
|
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
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
)
|