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
|
@@ -8,6 +8,7 @@ from dataclasses import dataclass
|
|
|
8
8
|
|
|
9
9
|
DEFAULT_ROW_RETENTION_LIMIT = 250_000
|
|
10
10
|
ROW_RETENTION_DELETE_FRACTION = 0.20
|
|
11
|
+
TOMBSTONE_STATUSES = ("archived", "merged", "superseded", "expired")
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@dataclass(frozen=True, slots=True)
|
|
@@ -18,17 +19,32 @@ class RetentionTarget:
|
|
|
18
19
|
table_name: str
|
|
19
20
|
order_column: str
|
|
20
21
|
id_columns: tuple[str, ...]
|
|
22
|
+
priority_statuses: tuple[str, ...] = ()
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
RETENTION_TARGETS: tuple[RetentionTarget, ...] = (
|
|
24
|
-
RetentionTarget(
|
|
26
|
+
RetentionTarget(
|
|
27
|
+
"profiles",
|
|
28
|
+
"profiles",
|
|
29
|
+
"created_at",
|
|
30
|
+
("profile_id",),
|
|
31
|
+
priority_statuses=TOMBSTONE_STATUSES,
|
|
32
|
+
),
|
|
25
33
|
RetentionTarget("interactions", "interactions", "created_at", ("interaction_id",)),
|
|
26
34
|
RetentionTarget("requests", "requests", "created_at", ("request_id",)),
|
|
27
35
|
RetentionTarget(
|
|
28
|
-
"user_playbooks",
|
|
36
|
+
"user_playbooks",
|
|
37
|
+
"user_playbooks",
|
|
38
|
+
"created_at",
|
|
39
|
+
("user_playbook_id",),
|
|
40
|
+
priority_statuses=TOMBSTONE_STATUSES,
|
|
29
41
|
),
|
|
30
42
|
RetentionTarget(
|
|
31
|
-
"agent_playbooks",
|
|
43
|
+
"agent_playbooks",
|
|
44
|
+
"agent_playbooks",
|
|
45
|
+
"created_at",
|
|
46
|
+
("agent_playbook_id",),
|
|
47
|
+
priority_statuses=TOMBSTONE_STATUSES,
|
|
32
48
|
),
|
|
33
49
|
RetentionTarget(
|
|
34
50
|
"agent_success_evaluation_result",
|
|
@@ -36,6 +52,25 @@ RETENTION_TARGETS: tuple[RetentionTarget, ...] = (
|
|
|
36
52
|
"created_at",
|
|
37
53
|
("result_id",),
|
|
38
54
|
),
|
|
55
|
+
# Grouped session target: keyed on (user_id, session_id) — not result_id —
|
|
56
|
+
# so retention always removes whole session snapshots, never a partial
|
|
57
|
+
# per-learning subset. Rows within a session share created_at (earliest
|
|
58
|
+
# request timestamp), so ordering keeps groups adjacent. The session's
|
|
59
|
+
# retrieved-eval _operation_state row is intentionally left in place: it
|
|
60
|
+
# is content-free (digest + counters) and self-heals on the next
|
|
61
|
+
# publish/forced evaluation.
|
|
62
|
+
RetentionTarget(
|
|
63
|
+
"retrieved_learning_evaluation",
|
|
64
|
+
"retrieved_learning_evaluation",
|
|
65
|
+
"created_at",
|
|
66
|
+
("user_id", "session_id"),
|
|
67
|
+
),
|
|
68
|
+
RetentionTarget(
|
|
69
|
+
"offline_tuner_reward_label",
|
|
70
|
+
"offline_tuner_reward_label",
|
|
71
|
+
"label_created_at",
|
|
72
|
+
("reward_label_id",),
|
|
73
|
+
),
|
|
39
74
|
RetentionTarget("share_links", "share_links", "created_at", ("id",)),
|
|
40
75
|
RetentionTarget(
|
|
41
76
|
"agent_playbook_source_user_playbooks",
|
|
@@ -117,6 +152,9 @@ RETENTION_CASCADES: dict[str, tuple[CascadeRef, ...]] = {
|
|
|
117
152
|
"playbook_retrieval_logs": (
|
|
118
153
|
CascadeRef("playbook_retrieval_log_items", "retrieval_log_id"),
|
|
119
154
|
),
|
|
155
|
+
"offline_tuner_reward_label": (
|
|
156
|
+
CascadeRef("offline_tuner_reward_label_target", "reward_label_id"),
|
|
157
|
+
),
|
|
120
158
|
}
|
|
121
159
|
|
|
122
160
|
|
|
@@ -98,12 +98,40 @@ class RetentionMixin(ABC):
|
|
|
98
98
|
target = get_retention_target(target_name)
|
|
99
99
|
if not self._retention_table_exists(target.table_name):
|
|
100
100
|
return 0
|
|
101
|
-
keys = self.
|
|
101
|
+
keys = self._retention_select_keys(target, count)
|
|
102
102
|
if not keys:
|
|
103
103
|
return 0
|
|
104
104
|
self._retention_perform_delete(target, keys)
|
|
105
105
|
return len(keys)
|
|
106
106
|
|
|
107
|
+
def _retention_select_keys(
|
|
108
|
+
self, target: RetentionTarget, count: int
|
|
109
|
+
) -> list[tuple[Any, ...]]:
|
|
110
|
+
"""Select tombstones first, then oldest rows when a target opts in.
|
|
111
|
+
|
|
112
|
+
The tombstone pass can never under-delete: the fallback select returns
|
|
113
|
+
``min(count, table_rows)`` keys and ``seen`` only ever holds tombstones
|
|
114
|
+
already counted, so the union still reaches ``count`` whenever the
|
|
115
|
+
table holds that many rows.
|
|
116
|
+
"""
|
|
117
|
+
if not target.priority_statuses:
|
|
118
|
+
return self._retention_select_oldest_keys(target, count)
|
|
119
|
+
|
|
120
|
+
keys = self._retention_select_oldest_keys(
|
|
121
|
+
target, count, statuses=target.priority_statuses
|
|
122
|
+
)
|
|
123
|
+
if len(keys) >= count:
|
|
124
|
+
return keys
|
|
125
|
+
|
|
126
|
+
seen = set(keys)
|
|
127
|
+
for key in self._retention_select_oldest_keys(target, count):
|
|
128
|
+
if key not in seen:
|
|
129
|
+
keys.append(key)
|
|
130
|
+
seen.add(key)
|
|
131
|
+
if len(keys) == count:
|
|
132
|
+
break
|
|
133
|
+
return keys
|
|
134
|
+
|
|
107
135
|
def _retention_perform_delete(
|
|
108
136
|
self, target: RetentionTarget, keys: list[tuple[Any, ...]]
|
|
109
137
|
) -> None:
|
|
@@ -130,13 +158,23 @@ class RetentionMixin(ABC):
|
|
|
130
158
|
|
|
131
159
|
@abstractmethod
|
|
132
160
|
def _retention_select_oldest_keys(
|
|
133
|
-
self,
|
|
161
|
+
self,
|
|
162
|
+
target: RetentionTarget,
|
|
163
|
+
count: int,
|
|
164
|
+
statuses: tuple[str, ...] | None = None,
|
|
134
165
|
) -> list[tuple[Any, ...]]:
|
|
135
166
|
"""Return up to ``count`` oldest key tuples for ``target``.
|
|
136
167
|
|
|
137
168
|
Each key tuple contains values aligned with ``target.id_columns``.
|
|
138
169
|
Ordering is by ``target.order_column`` ascending with the id
|
|
139
170
|
columns as a stable tiebreaker.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
target (RetentionTarget): Target whose keys are being selected.
|
|
174
|
+
count (int): Maximum number of key tuples to return.
|
|
175
|
+
statuses (tuple[str, ...] | None): When not None, restrict the
|
|
176
|
+
select to rows whose ``status`` is one of these values. An
|
|
177
|
+
empty tuple matches nothing (never every row).
|
|
140
178
|
"""
|
|
141
179
|
raise NotImplementedError
|
|
142
180
|
|
|
@@ -9,6 +9,7 @@ from ._base import (
|
|
|
9
9
|
)
|
|
10
10
|
from ._extras import ExtrasMixin
|
|
11
11
|
from ._governance import SQLiteGovernanceMixin
|
|
12
|
+
from ._learning_jobs import SQLiteLearningJobStoreMixin
|
|
12
13
|
from ._lineage import SQLiteLineageMixin
|
|
13
14
|
from ._operations import OperationMixin
|
|
14
15
|
from ._requests import RequestMixin
|
|
@@ -48,6 +49,7 @@ from .profiles import InteractionStoreMixin, ProfileSearchMixin, ProfileStoreMix
|
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
class SQLiteStorage(
|
|
52
|
+
SQLiteLearningJobStoreMixin,
|
|
51
53
|
SQLiteAgentRunStoreMixin,
|
|
52
54
|
SQLitePendingToolCallStoreMixin,
|
|
53
55
|
SQLiteRunToolDependencyStoreMixin,
|
|
@@ -7,6 +7,7 @@ are available.
|
|
|
7
7
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import contextlib
|
|
10
11
|
import functools
|
|
11
12
|
import json
|
|
12
13
|
import logging
|
|
@@ -14,7 +15,7 @@ import math
|
|
|
14
15
|
import re
|
|
15
16
|
import sqlite3
|
|
16
17
|
import threading
|
|
17
|
-
from collections.abc import Callable, Sequence
|
|
18
|
+
from collections.abc import Callable, Generator, Sequence
|
|
18
19
|
from datetime import UTC, datetime
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
from typing import Any, ClassVar, Literal
|
|
@@ -29,6 +30,7 @@ from reflexio.models.api_schema.service_schemas import (
|
|
|
29
30
|
ProfileTimeToLive,
|
|
30
31
|
RegularVsShadow,
|
|
31
32
|
Request,
|
|
33
|
+
RetrievedLearning,
|
|
32
34
|
Status,
|
|
33
35
|
ToolUsed,
|
|
34
36
|
UserActionType,
|
|
@@ -455,6 +457,12 @@ def _row_to_interaction(row: sqlite3.Row) -> Interaction:
|
|
|
455
457
|
if citations_raw and isinstance(citations_raw, list)
|
|
456
458
|
else []
|
|
457
459
|
)
|
|
460
|
+
retrieved_learnings_raw = _json_loads(d.get("retrieved_learnings"))
|
|
461
|
+
retrieved_learnings = (
|
|
462
|
+
[RetrievedLearning(**c) for c in retrieved_learnings_raw if isinstance(c, dict)]
|
|
463
|
+
if retrieved_learnings_raw and isinstance(retrieved_learnings_raw, list)
|
|
464
|
+
else []
|
|
465
|
+
)
|
|
458
466
|
return Interaction(
|
|
459
467
|
interaction_id=d["interaction_id"],
|
|
460
468
|
user_id=d["user_id"],
|
|
@@ -470,6 +478,7 @@ def _row_to_interaction(row: sqlite3.Row) -> Interaction:
|
|
|
470
478
|
expert_content=d.get("expert_content") or "",
|
|
471
479
|
tools_used=tools_used,
|
|
472
480
|
citations=citations,
|
|
481
|
+
retrieved_learnings=retrieved_learnings,
|
|
473
482
|
)
|
|
474
483
|
|
|
475
484
|
|
|
@@ -590,6 +599,7 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
590
599
|
# FTS/vec index helpers provided by SQLiteFtsVecMixin via the composed
|
|
591
600
|
# SQLiteStorage MRO; declared here for _migrate_vec_tables's benefit.
|
|
592
601
|
_vec_upsert: Any
|
|
602
|
+
_flush_index_op: Any
|
|
593
603
|
|
|
594
604
|
@staticmethod
|
|
595
605
|
def handle_exceptions(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
@@ -633,6 +643,10 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
633
643
|
|
|
634
644
|
self.db_path = db_path
|
|
635
645
|
self._lock = threading.RLock()
|
|
646
|
+
self._scope_depth = 0
|
|
647
|
+
self._deferred_index_ops: list[
|
|
648
|
+
tuple[str, Any]
|
|
649
|
+
] = [] # (kind, args) flushed post-commit
|
|
636
650
|
|
|
637
651
|
logger.info("SQLite Storage for org %s using db_path: %s", org_id, db_path)
|
|
638
652
|
|
|
@@ -656,6 +670,16 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
656
670
|
api_key_config=self.api_key_config,
|
|
657
671
|
)
|
|
658
672
|
self.embedding_dimensions = EMBEDDING_DIMENSIONS
|
|
673
|
+
# Text-generation model for storage-time document expansion. The
|
|
674
|
+
# shared self.llm_client is pinned to the EMBEDDING model, which is
|
|
675
|
+
# not a chat model — expansion calls must override the model or they
|
|
676
|
+
# fail (e.g. local/minilm-l6-v2 cannot serve completions).
|
|
677
|
+
self._expansion_model_name = resolve_model_name(
|
|
678
|
+
ModelRole.GENERATION,
|
|
679
|
+
site_var_value=site_var.get("default_generation_model_name"),
|
|
680
|
+
config_override=llm_config.generation_model_name if llm_config else None,
|
|
681
|
+
api_key_config=self.api_key_config,
|
|
682
|
+
)
|
|
659
683
|
|
|
660
684
|
litellm_config = LiteLLMConfig(
|
|
661
685
|
model=self.embedding_model_name,
|
|
@@ -670,6 +694,45 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
670
694
|
# Create tables
|
|
671
695
|
self.migrate()
|
|
672
696
|
|
|
697
|
+
# ------------------------------------------------------------------
|
|
698
|
+
# Transaction scope
|
|
699
|
+
# ------------------------------------------------------------------
|
|
700
|
+
|
|
701
|
+
def _own_transaction(self) -> bool:
|
|
702
|
+
"""True when the caller is NOT inside a commit_scope (owns its BEGIN/commit)."""
|
|
703
|
+
return self._scope_depth == 0
|
|
704
|
+
|
|
705
|
+
@contextlib.contextmanager
|
|
706
|
+
def commit_scope(self) -> Generator[None, None, None]:
|
|
707
|
+
"""Group writes into one atomic commit; defer FTS/vec index ops.
|
|
708
|
+
|
|
709
|
+
Nested scopes join the outermost: only the outer scope commits. On
|
|
710
|
+
exception the whole transaction rolls back and deferred index ops
|
|
711
|
+
are discarded.
|
|
712
|
+
"""
|
|
713
|
+
with self._lock:
|
|
714
|
+
if self._scope_depth > 0:
|
|
715
|
+
self._scope_depth += 1
|
|
716
|
+
try:
|
|
717
|
+
yield
|
|
718
|
+
finally:
|
|
719
|
+
self._scope_depth -= 1
|
|
720
|
+
return
|
|
721
|
+
self.conn.execute("BEGIN IMMEDIATE")
|
|
722
|
+
self._scope_depth = 1
|
|
723
|
+
try:
|
|
724
|
+
yield
|
|
725
|
+
self.conn.commit()
|
|
726
|
+
ops, self._deferred_index_ops = self._deferred_index_ops, []
|
|
727
|
+
for kind, args in ops:
|
|
728
|
+
self._flush_index_op(kind, args) # self-commits — fine post-commit
|
|
729
|
+
except Exception:
|
|
730
|
+
self.conn.rollback()
|
|
731
|
+
self._deferred_index_ops = []
|
|
732
|
+
raise
|
|
733
|
+
finally:
|
|
734
|
+
self._scope_depth = 0
|
|
735
|
+
|
|
673
736
|
# ------------------------------------------------------------------
|
|
674
737
|
# DDL / migration
|
|
675
738
|
# ------------------------------------------------------------------
|
|
@@ -714,6 +777,7 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
714
777
|
self._migrate_retire_profile_change_logs()
|
|
715
778
|
self._migrate_retire_playbook_aggregation_change_logs()
|
|
716
779
|
init_stall_state_table(self.conn)
|
|
780
|
+
self._migrate_learning_jobs()
|
|
717
781
|
return True
|
|
718
782
|
|
|
719
783
|
def _try_load_sqlite_vec(self) -> bool:
|
|
@@ -809,6 +873,14 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
809
873
|
)
|
|
810
874
|
self.conn.commit()
|
|
811
875
|
|
|
876
|
+
if "retrieved_learnings" not in columns:
|
|
877
|
+
logger.info("Adding retrieved_learnings column to interactions table.")
|
|
878
|
+
with self._lock:
|
|
879
|
+
self.conn.execute(
|
|
880
|
+
"ALTER TABLE interactions ADD COLUMN retrieved_learnings TEXT"
|
|
881
|
+
)
|
|
882
|
+
self.conn.commit()
|
|
883
|
+
|
|
812
884
|
def _migrate_feedback_schema(self) -> None:
|
|
813
885
|
"""Drop old-schema feedback/playbook tables so _DDL can recreate them.
|
|
814
886
|
|
|
@@ -1261,6 +1333,70 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1261
1333
|
)
|
|
1262
1334
|
self.conn.commit()
|
|
1263
1335
|
|
|
1336
|
+
def _migrate_learning_jobs(self) -> None:
|
|
1337
|
+
"""Create the learning_jobs table + partial indexes if missing (idempotent).
|
|
1338
|
+
|
|
1339
|
+
Runs ``CREATE TABLE IF NOT EXISTS`` and ``CREATE INDEX IF NOT EXISTS`` only —
|
|
1340
|
+
both are no-ops when the table / indexes already exist. New columns added in
|
|
1341
|
+
subsequent tasks are backfilled via ``PRAGMA table_info`` + ``ALTER TABLE … ADD
|
|
1342
|
+
COLUMN`` (mirroring ``_migrate_lineage_event_table``), because
|
|
1343
|
+
``CREATE TABLE IF NOT EXISTS`` silently skips the DDL on existing databases.
|
|
1344
|
+
|
|
1345
|
+
Called at the end of migrate() so the table is always present on startup.
|
|
1346
|
+
"""
|
|
1347
|
+
with self._lock:
|
|
1348
|
+
self.conn.executescript("""
|
|
1349
|
+
CREATE TABLE IF NOT EXISTS learning_jobs (
|
|
1350
|
+
job_id TEXT PRIMARY KEY,
|
|
1351
|
+
org_id TEXT NOT NULL,
|
|
1352
|
+
user_id TEXT NOT NULL,
|
|
1353
|
+
job_type TEXT NOT NULL DEFAULT 'learning',
|
|
1354
|
+
latest_request_id TEXT,
|
|
1355
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
1356
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
1357
|
+
max_attempts INTEGER NOT NULL DEFAULT 3,
|
|
1358
|
+
claimed_by TEXT,
|
|
1359
|
+
claim_token TEXT,
|
|
1360
|
+
claim_expires_at TEXT,
|
|
1361
|
+
covers_through TEXT,
|
|
1362
|
+
force_extraction INTEGER NOT NULL DEFAULT 0,
|
|
1363
|
+
skip_aggregation INTEGER NOT NULL DEFAULT 0,
|
|
1364
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
1365
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
1366
|
+
);
|
|
1367
|
+
CREATE UNIQUE INDEX IF NOT EXISTS learning_jobs_coalesce
|
|
1368
|
+
ON learning_jobs (org_id, user_id, job_type) WHERE status = 'pending';
|
|
1369
|
+
-- Recreate the poll index with 'failed' in the predicate. CREATE
|
|
1370
|
+
-- INDEX IF NOT EXISTS is a no-op on an existing index with the old
|
|
1371
|
+
-- ('pending','claimed') predicate, so DROP first to migrate it.
|
|
1372
|
+
DROP INDEX IF EXISTS learning_jobs_poll;
|
|
1373
|
+
CREATE INDEX IF NOT EXISTS learning_jobs_poll
|
|
1374
|
+
ON learning_jobs (created_at) WHERE status IN ('pending','failed','claimed');
|
|
1375
|
+
""")
|
|
1376
|
+
# Backfill columns added after the initial release (existing DBs skip
|
|
1377
|
+
# CREATE TABLE IF NOT EXISTS so these must be applied separately).
|
|
1378
|
+
existing_cols = {
|
|
1379
|
+
row["name"]
|
|
1380
|
+
for row in self.conn.execute(
|
|
1381
|
+
"PRAGMA table_info(learning_jobs)"
|
|
1382
|
+
).fetchall()
|
|
1383
|
+
}
|
|
1384
|
+
if "force_extraction" not in existing_cols:
|
|
1385
|
+
self.conn.execute(
|
|
1386
|
+
"ALTER TABLE learning_jobs ADD COLUMN force_extraction INTEGER NOT NULL DEFAULT 0"
|
|
1387
|
+
)
|
|
1388
|
+
if "skip_aggregation" not in existing_cols:
|
|
1389
|
+
self.conn.execute(
|
|
1390
|
+
"ALTER TABLE learning_jobs ADD COLUMN skip_aggregation INTEGER NOT NULL DEFAULT 0"
|
|
1391
|
+
)
|
|
1392
|
+
self.conn.commit()
|
|
1393
|
+
|
|
1394
|
+
def learning_jobs_columns(self) -> list[str]:
|
|
1395
|
+
"""Return the column names of the learning_jobs table."""
|
|
1396
|
+
with self._lock:
|
|
1397
|
+
rows = self.conn.execute("PRAGMA table_info(learning_jobs)").fetchall()
|
|
1398
|
+
return [row["name"] for row in rows]
|
|
1399
|
+
|
|
1264
1400
|
def _migrate_agent_playbook_source_windows(self) -> None:
|
|
1265
1401
|
"""Add source window snapshots to existing agent source mappings."""
|
|
1266
1402
|
cols = {
|
|
@@ -1451,7 +1587,8 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1451
1587
|
) -> sqlite3.Cursor:
|
|
1452
1588
|
with self._lock:
|
|
1453
1589
|
cur = self.conn.execute(sql, params)
|
|
1454
|
-
self.
|
|
1590
|
+
if self._own_transaction():
|
|
1591
|
+
self.conn.commit()
|
|
1455
1592
|
return cur
|
|
1456
1593
|
|
|
1457
1594
|
def _fetchone(
|
|
@@ -1519,6 +1656,7 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1519
1656
|
expander = DocumentExpander(
|
|
1520
1657
|
llm_client=self.llm_client,
|
|
1521
1658
|
prompt_manager=PromptManager(),
|
|
1659
|
+
model_name=self._expansion_model_name,
|
|
1522
1660
|
)
|
|
1523
1661
|
result = expander.expand(content)
|
|
1524
1662
|
return result.expanded_text or None
|
|
@@ -1737,6 +1875,7 @@ CREATE TABLE IF NOT EXISTS interactions (
|
|
|
1737
1875
|
expert_content TEXT NOT NULL DEFAULT '',
|
|
1738
1876
|
tools_used TEXT,
|
|
1739
1877
|
citations TEXT,
|
|
1878
|
+
retrieved_learnings TEXT,
|
|
1740
1879
|
embedding TEXT
|
|
1741
1880
|
);
|
|
1742
1881
|
CREATE INDEX IF NOT EXISTS idx_interactions_user_id ON interactions(user_id);
|
|
@@ -1838,6 +1977,28 @@ CREATE INDEX IF NOT EXISTS idx_eval_agent_version_created_at_desc
|
|
|
1838
1977
|
CREATE INDEX IF NOT EXISTS idx_eval_identity_created_at_desc
|
|
1839
1978
|
ON agent_success_evaluation_result(user_id, session_id, evaluation_name, agent_version, created_at DESC);
|
|
1840
1979
|
|
|
1980
|
+
CREATE TABLE IF NOT EXISTS retrieved_learning_evaluation (
|
|
1981
|
+
result_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1982
|
+
user_id TEXT NOT NULL,
|
|
1983
|
+
session_id TEXT NOT NULL,
|
|
1984
|
+
agent_version TEXT NOT NULL DEFAULT '',
|
|
1985
|
+
kind TEXT NOT NULL,
|
|
1986
|
+
learning_id TEXT NOT NULL,
|
|
1987
|
+
is_relevant INTEGER,
|
|
1988
|
+
relevance_reason TEXT NOT NULL DEFAULT '',
|
|
1989
|
+
impact TEXT,
|
|
1990
|
+
impact_reason TEXT NOT NULL DEFAULT '',
|
|
1991
|
+
created_at INTEGER NOT NULL,
|
|
1992
|
+
governance_subject_ref TEXT,
|
|
1993
|
+
UNIQUE (user_id, session_id, kind, learning_id)
|
|
1994
|
+
);
|
|
1995
|
+
CREATE INDEX IF NOT EXISTS idx_rle_created_at_result_id
|
|
1996
|
+
ON retrieved_learning_evaluation(created_at DESC, result_id DESC);
|
|
1997
|
+
CREATE INDEX IF NOT EXISTS idx_rle_session_id
|
|
1998
|
+
ON retrieved_learning_evaluation(session_id);
|
|
1999
|
+
CREATE INDEX IF NOT EXISTS idx_rle_subject_ref
|
|
2000
|
+
ON retrieved_learning_evaluation(governance_subject_ref);
|
|
2001
|
+
|
|
1841
2002
|
CREATE TABLE IF NOT EXISTS agent_playbook_source_user_playbooks (
|
|
1842
2003
|
agent_playbook_id INTEGER NOT NULL,
|
|
1843
2004
|
user_playbook_id INTEGER NOT NULL,
|
|
@@ -2094,4 +2255,31 @@ CREATE TABLE IF NOT EXISTS lineage_event (
|
|
|
2094
2255
|
);
|
|
2095
2256
|
CREATE INDEX IF NOT EXISTS idx_lineage_entity ON lineage_event (entity_type, entity_id);
|
|
2096
2257
|
|
|
2258
|
+
-- ============================================================================
|
|
2259
|
+
-- Durable learning pipeline — cross-org job queue
|
|
2260
|
+
-- ============================================================================
|
|
2261
|
+
|
|
2262
|
+
CREATE TABLE IF NOT EXISTS learning_jobs (
|
|
2263
|
+
job_id TEXT PRIMARY KEY,
|
|
2264
|
+
org_id TEXT NOT NULL,
|
|
2265
|
+
user_id TEXT NOT NULL,
|
|
2266
|
+
job_type TEXT NOT NULL DEFAULT 'learning',
|
|
2267
|
+
latest_request_id TEXT,
|
|
2268
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
2269
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
2270
|
+
max_attempts INTEGER NOT NULL DEFAULT 3,
|
|
2271
|
+
claimed_by TEXT,
|
|
2272
|
+
claim_token TEXT,
|
|
2273
|
+
claim_expires_at TEXT,
|
|
2274
|
+
covers_through TEXT,
|
|
2275
|
+
force_extraction INTEGER NOT NULL DEFAULT 0,
|
|
2276
|
+
skip_aggregation INTEGER NOT NULL DEFAULT 0,
|
|
2277
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
2278
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
2279
|
+
);
|
|
2280
|
+
CREATE UNIQUE INDEX IF NOT EXISTS learning_jobs_coalesce
|
|
2281
|
+
ON learning_jobs (org_id, user_id, job_type) WHERE status = 'pending';
|
|
2282
|
+
CREATE INDEX IF NOT EXISTS learning_jobs_poll
|
|
2283
|
+
ON learning_jobs (created_at) WHERE status IN ('pending','failed','claimed');
|
|
2284
|
+
|
|
2097
2285
|
"""
|
|
@@ -25,7 +25,14 @@ from ._base import (
|
|
|
25
25
|
_row_to_interaction,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
type _CitationKind = Literal["playbook", "profile"]
|
|
28
|
+
type _CitationKind = Literal["playbook", "profile", "user_playbook", "agent_playbook"]
|
|
29
|
+
|
|
30
|
+
_SUPPORTED_CITATION_KINDS: tuple[_CitationKind, ...] = (
|
|
31
|
+
"playbook",
|
|
32
|
+
"profile",
|
|
33
|
+
"user_playbook",
|
|
34
|
+
"agent_playbook",
|
|
35
|
+
)
|
|
29
36
|
|
|
30
37
|
|
|
31
38
|
class ExtrasMixin:
|
|
@@ -291,7 +298,7 @@ class ExtrasMixin:
|
|
|
291
298
|
continue
|
|
292
299
|
kind = c.get("kind")
|
|
293
300
|
real_id = c.get("real_id")
|
|
294
|
-
if kind not in
|
|
301
|
+
if kind not in _SUPPORTED_CITATION_KINDS or not real_id:
|
|
295
302
|
continue
|
|
296
303
|
key: tuple[_CitationKind, str] = (
|
|
297
304
|
cast(_CitationKind, kind),
|
|
@@ -453,12 +460,12 @@ class ExtrasMixin:
|
|
|
453
460
|
continue
|
|
454
461
|
kind = citation.get("kind")
|
|
455
462
|
real_id = citation.get("real_id")
|
|
456
|
-
if kind and real_id:
|
|
463
|
+
if kind in _SUPPORTED_CITATION_KINDS and real_id:
|
|
457
464
|
out.append(
|
|
458
465
|
SessionCitation(
|
|
459
466
|
user_id=row["user_id"],
|
|
460
467
|
session_id=row["session_id"],
|
|
461
|
-
kind=
|
|
468
|
+
kind=cast(_CitationKind, kind),
|
|
462
469
|
real_id=str(real_id),
|
|
463
470
|
title=str(citation.get("title") or ""),
|
|
464
471
|
)
|
package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py
CHANGED
|
@@ -392,11 +392,27 @@ class SQLiteGovernanceMixin:
|
|
|
392
392
|
WHERE user_id = ?""",
|
|
393
393
|
(user_id,),
|
|
394
394
|
).fetchone()
|
|
395
|
+
rle_result_row = self.conn.execute(
|
|
396
|
+
"""SELECT COUNT(*) AS cnt
|
|
397
|
+
FROM retrieved_learning_evaluation
|
|
398
|
+
WHERE user_id = ?""",
|
|
399
|
+
(user_id,),
|
|
400
|
+
).fetchone()
|
|
401
|
+
session_count_row = self.conn.execute(
|
|
402
|
+
"SELECT COUNT(DISTINCT session_id) AS cnt FROM requests WHERE user_id = ?",
|
|
403
|
+
(user_id,),
|
|
404
|
+
).fetchone()
|
|
395
405
|
profile_rows = self.conn.execute(
|
|
396
406
|
"SELECT profile_id FROM profiles WHERE user_id = ?",
|
|
397
407
|
(user_id,),
|
|
398
408
|
).fetchall()
|
|
399
|
-
if
|
|
409
|
+
if (
|
|
410
|
+
request_row is None
|
|
411
|
+
or interaction_row is None
|
|
412
|
+
or eval_result_row is None
|
|
413
|
+
or rle_result_row is None
|
|
414
|
+
or session_count_row is None
|
|
415
|
+
):
|
|
400
416
|
raise ValueError("Missing governance count rows")
|
|
401
417
|
profile_ids = [str(row["profile_id"]) for row in profile_rows]
|
|
402
418
|
purge_profile_ids, delete_profile_ids = self._deps()._partition_purge_vs_delete(
|
|
@@ -420,6 +436,17 @@ class SQLiteGovernanceMixin:
|
|
|
420
436
|
"profile_purge": len(purge_profile_ids),
|
|
421
437
|
"user_playbook": len(delete_playbook_ids),
|
|
422
438
|
"agent_success_evaluation_result": int(eval_result_row["cnt"]),
|
|
439
|
+
# Offline tuner tables are enterprise-only (tenant stream); the
|
|
440
|
+
# OSS SQLite backend has no such tables, so the planned and
|
|
441
|
+
# deleted counts are structurally zero. The delete-target matrix
|
|
442
|
+
# validator still requires the target rows to exist.
|
|
443
|
+
"offline_tuner_reward_label": 0,
|
|
444
|
+
"offline_tuner_reward_label_target_by_target_owner": 0,
|
|
445
|
+
"retrieved_learning_evaluation_result": int(rle_result_row["cnt"]),
|
|
446
|
+
# Planned as an upper bound: up to 3 evaluation state namespaces
|
|
447
|
+
# per session (retrieved-eval state, agent-success marker,
|
|
448
|
+
# grade-cache rows). The delete phase reports the exact count.
|
|
449
|
+
"evaluation_operation_state": 3 * int(session_count_row["cnt"]),
|
|
423
450
|
"user_playbook_purge": len(purge_playbook_ids),
|
|
424
451
|
}
|
|
425
452
|
|