claude-smart 0.2.49 → 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 +10 -43
- package/bin/claude-smart.js +105 -0
- 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/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +197 -1
- package/plugin/scripts/backend-python-runner.py +46 -0
- package/plugin/scripts/backend-service.sh +757 -119
- package/plugin/scripts/codex-hook.js +63 -225
- package/plugin/scripts/dashboard-open.sh +6 -4
- package/plugin/scripts/dashboard-service.sh +117 -136
- package/plugin/scripts/hook_entry.sh +3 -0
- package/plugin/scripts/smart-install.sh +15 -1
- package/plugin/src/claude_smart/cli.py +14 -0
- package/plugin/src/claude_smart/context_inject.py +3 -0
- package/plugin/src/claude_smart/env_config.py +4 -1
- 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 +1 -1
- 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/client/client.py +126 -3
- 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 +177 -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 +66 -3
- 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 +62 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
- 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 +20 -5
- 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/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/worker.py +85 -16
- 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_worker.py +66 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +510 -63
- 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 +122 -44
- 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 +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +50 -0
- 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 +36 -1
- 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/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 +43 -0
- 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/_user.py +45 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +146 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +42 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +1 -1
- 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 +50 -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 +59 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +52 -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
|
@@ -27,6 +27,16 @@ from .._base import (
|
|
|
27
27
|
logger = logging.getLogger(__name__)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def _embed_text_for_interaction(content: str | None, action_desc: str | None) -> str:
|
|
31
|
+
"""Derive the text embedded for an interaction.
|
|
32
|
+
|
|
33
|
+
Kept byte-for-byte identical to the ingest path
|
|
34
|
+
(``add_user_interactions_bulk`` / ``prepare_interaction_embeddings``) so a
|
|
35
|
+
backfilled vector matches a freshly-ingested one.
|
|
36
|
+
"""
|
|
37
|
+
return "\n".join([content or "", action_desc or ""])
|
|
38
|
+
|
|
39
|
+
|
|
30
40
|
class InteractionStoreMixin:
|
|
31
41
|
"""Mixin providing interaction-store CRUD for SQLite storage."""
|
|
32
42
|
|
|
@@ -72,6 +82,12 @@ class InteractionStoreMixin:
|
|
|
72
82
|
|
|
73
83
|
@SQLiteStorageBase.handle_exceptions
|
|
74
84
|
def add_user_interaction(self, user_id: str, interaction: Interaction) -> None: # noqa: ARG002
|
|
85
|
+
# NOTE: distinct from the bulk/backfill derivation
|
|
86
|
+
# (_embed_text_for_interaction). This legacy single-insert path embeds via
|
|
87
|
+
# the purpose-prefixed _get_embedding ("search_document: ...") and its own
|
|
88
|
+
# f-string, so it is intentionally NOT routed through the shared helper —
|
|
89
|
+
# aligning it would silently change stored embedding values (e.g. null ->
|
|
90
|
+
# literal "None"). The real bulk ingest + backfill share the helper.
|
|
75
91
|
embedding = self._get_embedding(
|
|
76
92
|
f"{interaction.content}\n{interaction.user_action_description}"
|
|
77
93
|
)
|
|
@@ -93,8 +109,9 @@ class InteractionStoreMixin:
|
|
|
93
109
|
(interaction_id, user_id, content, request_id, created_at,
|
|
94
110
|
role, user_action, user_action_description,
|
|
95
111
|
interacted_image_url, image_encoding, shadow_content,
|
|
96
|
-
expert_content, tools_used, citations,
|
|
97
|
-
|
|
112
|
+
expert_content, tools_used, citations, retrieved_learnings,
|
|
113
|
+
embedding, governance_subject_ref)
|
|
114
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
|
98
115
|
(
|
|
99
116
|
interaction.interaction_id,
|
|
100
117
|
interaction.user_id,
|
|
@@ -114,6 +131,12 @@ class InteractionStoreMixin:
|
|
|
114
131
|
_json_dumps(
|
|
115
132
|
[c.model_dump() for c in interaction.citations]
|
|
116
133
|
),
|
|
134
|
+
_json_dumps(
|
|
135
|
+
[
|
|
136
|
+
c.model_dump()
|
|
137
|
+
for c in interaction.retrieved_learnings
|
|
138
|
+
]
|
|
139
|
+
),
|
|
117
140
|
_json_dumps(interaction.embedding),
|
|
118
141
|
subject_ref,
|
|
119
142
|
),
|
|
@@ -125,8 +148,9 @@ class InteractionStoreMixin:
|
|
|
125
148
|
(user_id, content, request_id, created_at,
|
|
126
149
|
role, user_action, user_action_description,
|
|
127
150
|
interacted_image_url, image_encoding, shadow_content,
|
|
128
|
-
expert_content, tools_used, citations,
|
|
129
|
-
|
|
151
|
+
expert_content, tools_used, citations, retrieved_learnings,
|
|
152
|
+
embedding, governance_subject_ref)
|
|
153
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
|
130
154
|
(
|
|
131
155
|
interaction.user_id,
|
|
132
156
|
interaction.content,
|
|
@@ -145,6 +169,12 @@ class InteractionStoreMixin:
|
|
|
145
169
|
_json_dumps(
|
|
146
170
|
[c.model_dump() for c in interaction.citations]
|
|
147
171
|
),
|
|
172
|
+
_json_dumps(
|
|
173
|
+
[
|
|
174
|
+
c.model_dump()
|
|
175
|
+
for c in interaction.retrieved_learnings
|
|
176
|
+
]
|
|
177
|
+
),
|
|
148
178
|
_json_dumps(interaction.embedding),
|
|
149
179
|
subject_ref,
|
|
150
180
|
),
|
|
@@ -186,7 +216,7 @@ class InteractionStoreMixin:
|
|
|
186
216
|
to_embed = [i for i in interactions if not i.embedding]
|
|
187
217
|
if to_embed:
|
|
188
218
|
texts = [
|
|
189
|
-
|
|
219
|
+
_embed_text_for_interaction(i.content, i.user_action_description)
|
|
190
220
|
for i in to_embed
|
|
191
221
|
]
|
|
192
222
|
try:
|
|
@@ -218,7 +248,7 @@ class InteractionStoreMixin:
|
|
|
218
248
|
if not to_embed:
|
|
219
249
|
return
|
|
220
250
|
texts = [
|
|
221
|
-
|
|
251
|
+
_embed_text_for_interaction(i.content, i.user_action_description)
|
|
222
252
|
for i in to_embed
|
|
223
253
|
]
|
|
224
254
|
try:
|
|
@@ -235,6 +265,116 @@ class InteractionStoreMixin:
|
|
|
235
265
|
for interaction, embedding in zip(to_embed, embeddings, strict=False):
|
|
236
266
|
interaction.embedding = embedding
|
|
237
267
|
|
|
268
|
+
# ------------------------------------------------------------------
|
|
269
|
+
# Missing-vector backfill (durability sweep)
|
|
270
|
+
# ------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
@SQLiteStorageBase.handle_exceptions
|
|
273
|
+
def iter_interactions_missing_vectors(self, limit: int) -> list[tuple[int, str]]:
|
|
274
|
+
"""Enumerate interactions whose embedding was never persisted.
|
|
275
|
+
|
|
276
|
+
A degraded/failed embedding leaves the ``embedding`` column empty
|
|
277
|
+
(``'[]'`` or NULL) and writes no ``interactions_vec`` row — equivalent
|
|
278
|
+
conditions on the write path. Detecting on the ``embedding`` column is
|
|
279
|
+
both the root-cause signal and portable across backends (the column
|
|
280
|
+
exists everywhere; ``interactions_vec`` is a SQLite-vec detail).
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
limit: Maximum number of interactions to return.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
list[tuple[int, str]]: ``(interaction_id, embed_text)`` pairs, at
|
|
287
|
+
most ``limit`` long, ordered by ``interaction_id`` for stable paging.
|
|
288
|
+
"""
|
|
289
|
+
if limit <= 0:
|
|
290
|
+
return []
|
|
291
|
+
rows = self._fetchall(
|
|
292
|
+
"""SELECT interaction_id, content, user_action_description
|
|
293
|
+
FROM interactions
|
|
294
|
+
WHERE embedding IS NULL OR embedding = '[]'
|
|
295
|
+
ORDER BY interaction_id
|
|
296
|
+
LIMIT ?""",
|
|
297
|
+
(limit,),
|
|
298
|
+
)
|
|
299
|
+
return [
|
|
300
|
+
(
|
|
301
|
+
r["interaction_id"],
|
|
302
|
+
_embed_text_for_interaction(r["content"], r["user_action_description"]),
|
|
303
|
+
)
|
|
304
|
+
for r in rows
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
@SQLiteStorageBase.handle_exceptions
|
|
308
|
+
def backfill_missing_interaction_vectors(self, limit: int) -> int:
|
|
309
|
+
"""Re-embed and persist vectors for interactions missing them.
|
|
310
|
+
|
|
311
|
+
Bounded by ``limit`` and idempotent: once the ``embedding`` column and
|
|
312
|
+
``interactions_vec`` row are written, the row no longer matches
|
|
313
|
+
detection and is skipped next time. Embeds via the same batch call and
|
|
314
|
+
text derivation the ingest path uses so backfilled vectors match
|
|
315
|
+
freshly-ingested ones.
|
|
316
|
+
|
|
317
|
+
Fail-safe: if the embedder is unavailable the batch call raises
|
|
318
|
+
``EmbeddingUnavailableError``; we log a single bounded WARN and return 0,
|
|
319
|
+
leaving the rows for the next tick rather than crashing the caller or
|
|
320
|
+
hot-looping a down embedder.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
limit: Maximum number of interactions to re-embed this call.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
int: Number of interactions whose vector was backfilled.
|
|
327
|
+
"""
|
|
328
|
+
if limit <= 0:
|
|
329
|
+
return 0
|
|
330
|
+
pairs = self.iter_interactions_missing_vectors(limit)
|
|
331
|
+
if not pairs:
|
|
332
|
+
return 0
|
|
333
|
+
texts = [text for _, text in pairs]
|
|
334
|
+
try:
|
|
335
|
+
embeddings = self.llm_client.get_embeddings(
|
|
336
|
+
texts, self.embedding_model_name, self.embedding_dimensions
|
|
337
|
+
)
|
|
338
|
+
except EmbeddingUnavailableError as exc:
|
|
339
|
+
logger.warning(
|
|
340
|
+
"Embedding unavailable during missing-vector backfill; leaving "
|
|
341
|
+
"%d interaction(s) for the next tick: %s",
|
|
342
|
+
len(pairs),
|
|
343
|
+
exc,
|
|
344
|
+
)
|
|
345
|
+
return 0
|
|
346
|
+
backfilled = 0
|
|
347
|
+
for (iid, _text), embedding in zip(pairs, embeddings, strict=False):
|
|
348
|
+
if not embedding:
|
|
349
|
+
# Embedder returned empty for this row — leave it for next tick.
|
|
350
|
+
continue
|
|
351
|
+
self._persist_backfilled_vector(iid, embedding)
|
|
352
|
+
backfilled += 1
|
|
353
|
+
return backfilled
|
|
354
|
+
|
|
355
|
+
def _persist_backfilled_vector(
|
|
356
|
+
self, interaction_id: int, embedding: list[float]
|
|
357
|
+
) -> None:
|
|
358
|
+
"""Write a re-embedded vector for one interaction (column + vec row).
|
|
359
|
+
|
|
360
|
+
Updates the ``embedding`` TEXT column (read by the Python vector-rank
|
|
361
|
+
search path) and upserts the ``interactions_vec`` row via the same
|
|
362
|
+
``_vec_upsert`` helper the insert path uses.
|
|
363
|
+
|
|
364
|
+
Only commits when it owns the transaction (mirroring ``_insert_interaction``)
|
|
365
|
+
so it can never flush a partial outer transaction if ever called inside an
|
|
366
|
+
open ``commit_scope``; ``_vec_upsert`` likewise defers under an open scope.
|
|
367
|
+
"""
|
|
368
|
+
with self._lock:
|
|
369
|
+
own_txn = self._own_transaction()
|
|
370
|
+
self.conn.execute(
|
|
371
|
+
"UPDATE interactions SET embedding = ? WHERE interaction_id = ?",
|
|
372
|
+
(_json_dumps(embedding), interaction_id),
|
|
373
|
+
)
|
|
374
|
+
if own_txn:
|
|
375
|
+
self.conn.commit()
|
|
376
|
+
self._vec_upsert("interactions_vec", interaction_id, embedding)
|
|
377
|
+
|
|
238
378
|
@SQLiteStorageBase.handle_exceptions
|
|
239
379
|
def delete_user_interaction(self, request: DeleteUserInteractionRequest) -> None:
|
|
240
380
|
with self._lock:
|
|
@@ -16,6 +16,9 @@ from reflexio.models.api_schema.service_schemas import (
|
|
|
16
16
|
Status,
|
|
17
17
|
UserProfile,
|
|
18
18
|
)
|
|
19
|
+
from reflexio.server.services.storage.lifecycle_filters import (
|
|
20
|
+
validate_include_inactive,
|
|
21
|
+
)
|
|
19
22
|
|
|
20
23
|
from .._base import (
|
|
21
24
|
_PROFILE_TOMBSTONE_STATUS_VALUES,
|
|
@@ -208,12 +211,14 @@ class ProfileStoreMixin:
|
|
|
208
211
|
sql = f"SELECT * FROM profiles WHERE {' AND '.join(conditions)}"
|
|
209
212
|
return [_row_to_profile(r) for r in self._fetchall(sql, all_params)]
|
|
210
213
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
def precompute_profile_embeddings(self, profiles: list[UserProfile]) -> None:
|
|
215
|
+
"""Populate ``.embedding`` / ``.expanded_terms`` in place; no DB write.
|
|
216
|
+
|
|
217
|
+
Extracted verbatim from the former ``add_user_profile`` prelude so the
|
|
218
|
+
durable compute/persist split can embed outside the writer transaction
|
|
219
|
+
and then persist with ``skip_embedding=True``.
|
|
220
|
+
"""
|
|
221
|
+
for profile in profiles:
|
|
217
222
|
embedding_text = "\n".join([profile.content, str(profile.custom_features)])
|
|
218
223
|
if self._should_expand_documents():
|
|
219
224
|
with ThreadPoolExecutor(max_workers=2) as executor:
|
|
@@ -223,6 +228,25 @@ class ProfileStoreMixin:
|
|
|
223
228
|
profile.expanded_terms = exp_future.result(timeout=15)
|
|
224
229
|
else:
|
|
225
230
|
profile.embedding = self._get_embedding(embedding_text)
|
|
231
|
+
|
|
232
|
+
@SQLiteStorageBase.handle_exceptions
|
|
233
|
+
def add_user_profile(
|
|
234
|
+
self,
|
|
235
|
+
user_id: str, # noqa: ARG002
|
|
236
|
+
user_profiles: list[UserProfile],
|
|
237
|
+
*,
|
|
238
|
+
skip_embedding: bool = False,
|
|
239
|
+
) -> None:
|
|
240
|
+
for profile in user_profiles:
|
|
241
|
+
subject_ref = self._subject_ref_for_user_id(profile.user_id)
|
|
242
|
+
with self._lock:
|
|
243
|
+
self._assert_subject_writable_locked(subject_ref)
|
|
244
|
+
# Default (skip_embedding=False) recomputes unconditionally, exactly
|
|
245
|
+
# as before — model_copy callers that change content while keeping
|
|
246
|
+
# the old embedding depend on this. The durable persist path opts
|
|
247
|
+
# out (embedding already set by precompute_profile_embeddings).
|
|
248
|
+
if not skip_embedding:
|
|
249
|
+
self.precompute_profile_embeddings([profile])
|
|
226
250
|
embedding = profile.embedding
|
|
227
251
|
with self._lock:
|
|
228
252
|
own_txn = self._own_transaction()
|
|
@@ -639,14 +663,25 @@ class ProfileStoreMixin:
|
|
|
639
663
|
user_id: str,
|
|
640
664
|
profile_ids: list[str],
|
|
641
665
|
status_filter: list[Status | None] | None = None,
|
|
666
|
+
*,
|
|
667
|
+
include_inactive: bool = False,
|
|
642
668
|
) -> list[UserProfile]:
|
|
669
|
+
validate_include_inactive(
|
|
670
|
+
include_inactive=include_inactive, status_filter=status_filter
|
|
671
|
+
)
|
|
643
672
|
if not profile_ids:
|
|
644
673
|
return []
|
|
674
|
+
ph = ",".join("?" for _ in profile_ids)
|
|
675
|
+
if include_inactive:
|
|
676
|
+
rows = self._fetchall(
|
|
677
|
+
f"SELECT * FROM profiles WHERE user_id = ? AND profile_id IN ({ph})",
|
|
678
|
+
(user_id, *profile_ids),
|
|
679
|
+
)
|
|
680
|
+
return [_row_to_profile(r) for r in rows]
|
|
645
681
|
if status_filter is None:
|
|
646
682
|
status_filter = [None]
|
|
647
683
|
current_ts = _epoch_now()
|
|
648
684
|
frag, sparams = _build_status_sql(status_filter)
|
|
649
|
-
ph = ",".join("?" for _ in profile_ids)
|
|
650
685
|
sql = (
|
|
651
686
|
f"SELECT * FROM profiles "
|
|
652
687
|
f"WHERE user_id = ? AND profile_id IN ({ph}) "
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from reflexio.models.api_schema.domain.enums import Status
|
|
2
2
|
|
|
3
|
-
from ._learning_jobs import LearningJob, LearningJobStatus, LearningJobStoreABC
|
|
4
3
|
from ._agent_run import (
|
|
5
4
|
NOT_APPLICABLE_ANSWER,
|
|
6
5
|
AgentBinding,
|
|
@@ -25,6 +24,7 @@ from ._agent_run import (
|
|
|
25
24
|
from ._base import BaseStorageCore, matches_status_filter
|
|
26
25
|
from ._commit_scope import CommitScopeMixin
|
|
27
26
|
from ._extras import ExtrasMixin
|
|
27
|
+
from ._learning_jobs import LearningJob, LearningJobStatus, LearningJobStoreABC
|
|
28
28
|
from ._lineage import EntityType, LineageEventMixin
|
|
29
29
|
from ._operations import OperationMixin
|
|
30
30
|
from ._requests import RequestMixin
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
|
+
from typing import cast
|
|
2
3
|
|
|
3
4
|
from reflexio.models.api_schema.braintrust_schema import (
|
|
4
5
|
BraintrustConnection,
|
|
5
6
|
ImportedScore,
|
|
6
7
|
)
|
|
7
8
|
from reflexio.models.api_schema.domain import (
|
|
9
|
+
CitationKind,
|
|
8
10
|
Interaction,
|
|
9
11
|
)
|
|
10
12
|
from reflexio.models.api_schema.internal_schema import SessionCitation
|
|
13
|
+
|
|
14
|
+
# Stored citation dicts are untyped JSON; only these kinds are valid.
|
|
15
|
+
CITATION_KINDS: frozenset[str] = frozenset(
|
|
16
|
+
{"playbook", "profile", "user_playbook", "agent_playbook"}
|
|
17
|
+
)
|
|
11
18
|
from reflexio.models.api_schema.retriever_schema import PlaybookApplicationStat
|
|
12
19
|
|
|
13
20
|
|
|
@@ -142,12 +149,12 @@ class ExtrasMixin:
|
|
|
142
149
|
kind = getattr(cite, "kind", None)
|
|
143
150
|
real_id = getattr(cite, "real_id", None)
|
|
144
151
|
title = getattr(cite, "title", "") or ""
|
|
145
|
-
if kind and real_id:
|
|
152
|
+
if str(kind) in CITATION_KINDS and real_id:
|
|
146
153
|
out.append(
|
|
147
154
|
SessionCitation(
|
|
148
155
|
user_id="",
|
|
149
156
|
session_id=session_id,
|
|
150
|
-
kind=str(kind),
|
|
157
|
+
kind=cast("CitationKind", str(kind)),
|
|
151
158
|
real_id=str(real_id),
|
|
152
159
|
title=str(title),
|
|
153
160
|
)
|
package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py
CHANGED
|
@@ -145,6 +145,7 @@ class LearningJobStoreABC(ABC):
|
|
|
145
145
|
job_id: str,
|
|
146
146
|
claim_token: str,
|
|
147
147
|
dead: bool,
|
|
148
|
+
refund_attempt: bool = False,
|
|
148
149
|
) -> None:
|
|
149
150
|
"""Fenced fail/dead transition — sets status, does NOT increment attempts.
|
|
150
151
|
|
|
@@ -157,6 +158,25 @@ class LearningJobStoreABC(ABC):
|
|
|
157
158
|
worker's dead-gate (``job.attempts >= max_attempts``) depends on that
|
|
158
159
|
single-increment-per-claim invariant. Incrementing here would
|
|
159
160
|
double-count and misfire the dead transition.
|
|
161
|
+
|
|
162
|
+
``refund_attempt`` (default ``False``): when ``True``, decrement
|
|
163
|
+
``attempts`` by one (floored at 0) as part of this transition. This is
|
|
164
|
+
the same-user-contention requeue path (F4): the worker claimed the job
|
|
165
|
+
(``claim_learning_jobs`` did ``attempts += 1``) but could not run because
|
|
166
|
+
another same-user job holds the per-user lock, so it releases the job
|
|
167
|
+
WITHOUT having done any real work. Refunding the claim's increment makes
|
|
168
|
+
a contention cycle (claim +1, contention-release -1) net to zero, so a
|
|
169
|
+
job that loses the same-user race repeatedly (every ~2s poll while the
|
|
170
|
+
holder runs its ~60s compute) does NOT inflate ``attempts`` past
|
|
171
|
+
``max_attempts`` and dead-letter with zero real retries. It is a no-op
|
|
172
|
+
unless ``refund_attempt=True``, so the ``dead`` retry path is unchanged.
|
|
173
|
+
|
|
174
|
+
# TASK 10: the enterprise Supabase (``reflexio_ext/server/services/
|
|
175
|
+
# storage/supabase_storage/_learning_jobs.py``) and native-Postgres
|
|
176
|
+
# ``fail_learning_job`` impls (and their ``claim_learning_jobs`` SQL
|
|
177
|
+
# functions) need this same ``refund_attempt`` decrement. The param is
|
|
178
|
+
# optional with a default so this base signature stays back-compatible
|
|
179
|
+
# until they are updated.
|
|
160
180
|
"""
|
|
161
181
|
raise NotImplementedError
|
|
162
182
|
|
|
@@ -179,6 +199,36 @@ class LearningJobStoreABC(ABC):
|
|
|
179
199
|
"""
|
|
180
200
|
raise NotImplementedError
|
|
181
201
|
|
|
202
|
+
@abstractmethod
|
|
203
|
+
def get_oldest_pending_learning_job_age_seconds(self) -> float | None:
|
|
204
|
+
"""Age in seconds of the oldest ``status='pending'`` job on this storage ref.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
The age in seconds (``now - created_at``) of the oldest pending job,
|
|
208
|
+
or ``None`` if there are no pending jobs.
|
|
209
|
+
|
|
210
|
+
Resolves the table via the same placement resolver used by
|
|
211
|
+
``list_org_ids_with_pending_learning_jobs`` — never hardcodes
|
|
212
|
+
``public.`` or ``reflexio_ops.``.
|
|
213
|
+
"""
|
|
214
|
+
raise NotImplementedError
|
|
215
|
+
|
|
216
|
+
@abstractmethod
|
|
217
|
+
def count_learning_jobs_by_status(self, status: str) -> int:
|
|
218
|
+
"""Count of learning jobs with the given ``status`` on this storage ref.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
status: One of ``'pending'``, ``'claimed'``, ``'done'``,
|
|
222
|
+
``'failed'``, or ``'dead'``.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
The integer count of matching rows (0 if none).
|
|
226
|
+
|
|
227
|
+
Resolves the table via the same placement resolver used by the other
|
|
228
|
+
query methods — never hardcodes schema prefix.
|
|
229
|
+
"""
|
|
230
|
+
raise NotImplementedError
|
|
231
|
+
|
|
182
232
|
@abstractmethod
|
|
183
233
|
def get_learning_status_for_request(
|
|
184
234
|
self,
|
|
@@ -146,6 +146,10 @@ class OperationMixin:
|
|
|
146
146
|
the current holder) are dropped to keep the queue idempotent under
|
|
147
147
|
publish retries.
|
|
148
148
|
|
|
149
|
+
``request_id`` is the intentional storage-boundary name here. Higher
|
|
150
|
+
layers may call the same value ``lock_request_id``, but persisted lock
|
|
151
|
+
rows and queued entries still use the legacy ``request_id`` key.
|
|
152
|
+
|
|
149
153
|
The queue is a FIFO drained one entry at a time when the holder
|
|
150
154
|
releases the lock. It replaces the older single-slot
|
|
151
155
|
``pending_request_id`` field, which silently dropped earlier blocked
|
|
@@ -178,6 +182,9 @@ class OperationMixin:
|
|
|
178
182
|
) -> bool:
|
|
179
183
|
"""Atomically clear an in-progress lock only if ``request_id`` still owns it.
|
|
180
184
|
|
|
185
|
+
The storage boundary keeps the legacy ``request_id`` naming for the
|
|
186
|
+
persisted lock holder even when callers use clearer local names.
|
|
187
|
+
|
|
181
188
|
Args:
|
|
182
189
|
state_key: Operation-state row key for the lock.
|
|
183
190
|
request_id: Request attempting to release the lock.
|
package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py
CHANGED
|
@@ -7,7 +7,9 @@ class RetrievalLogMixin:
|
|
|
7
7
|
These methods are optional retrieval-capture storage hooks. They are
|
|
8
8
|
intentionally concrete (not ``@abstractmethod``) so concrete storage classes
|
|
9
9
|
remain instantiable; each raises ``NotImplementedError`` until a backend
|
|
10
|
-
provides a real implementation.
|
|
10
|
+
provides a real implementation. Stored item rows may use either the legacy
|
|
11
|
+
``agent_playbook_id`` field or the explicit ``target_kind`` / ``target_id``
|
|
12
|
+
pair; readers should preserve backward compatibility for both.
|
|
11
13
|
"""
|
|
12
14
|
|
|
13
15
|
def save_playbook_retrieval_log(self, log: PlaybookRetrievalLog) -> int:
|
package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py
CHANGED
|
@@ -34,6 +34,10 @@ class ShadowVerdictsMixin:
|
|
|
34
34
|
clear error message naming the backend rather than ``AttributeError``.
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
+
def supports_shadow_comparison_verdicts(self) -> bool:
|
|
38
|
+
"""Return whether this backend can persist shadow comparison verdicts."""
|
|
39
|
+
return False
|
|
40
|
+
|
|
37
41
|
def save_shadow_comparison_verdict(
|
|
38
42
|
self, verdict: ShadowComparisonVerdict
|
|
39
43
|
) -> ShadowComparisonVerdict:
|
package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py
CHANGED
|
@@ -44,7 +44,12 @@ class RunToolDependencyKind(StrEnum):
|
|
|
44
44
|
|
|
45
45
|
@dataclass(frozen=True)
|
|
46
46
|
class AgentBinding:
|
|
47
|
-
"""Logical run binding flattened into `_agent_runs` storage columns.
|
|
47
|
+
"""Logical run binding flattened into `_agent_runs` storage columns.
|
|
48
|
+
|
|
49
|
+
``request_id`` remains the persisted provenance field name at this storage
|
|
50
|
+
boundary even when higher-level helpers refer to the same value as a
|
|
51
|
+
generation request id.
|
|
52
|
+
"""
|
|
48
53
|
|
|
49
54
|
org_id: str
|
|
50
55
|
extractor_kind: str
|
|
@@ -60,6 +65,12 @@ class AgentBinding:
|
|
|
60
65
|
|
|
61
66
|
@dataclass(frozen=True)
|
|
62
67
|
class AgentRunRecord:
|
|
68
|
+
"""Durable agent run row plus snapshots used for resume/finalization.
|
|
69
|
+
|
|
70
|
+
``generation_request_snapshot`` intentionally keeps a legacy ``request_id``
|
|
71
|
+
key for stored provenance.
|
|
72
|
+
"""
|
|
73
|
+
|
|
63
74
|
id: str
|
|
64
75
|
binding: AgentBinding
|
|
65
76
|
status: AgentRunStatus
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Single source of truth for evaluation ``_operation_state`` key formats.
|
|
2
|
+
|
|
3
|
+
Three evaluation namespaces store per-session state in ``_operation_state``:
|
|
4
|
+
|
|
5
|
+
1. ``agent_success_group_eval`` — the runner's "evaluated" marker.
|
|
6
|
+
2. ``grade_on_demand`` — the 24h on-demand grading cache.
|
|
7
|
+
3. ``retrieved_learning_eval`` — retrieved-learning generation/completion
|
|
8
|
+
state (see ``retrieved_learning_state.build_retrieved_learning_state_key``).
|
|
9
|
+
|
|
10
|
+
Governance erasure must scrub all three for an erased user's sessions, so the
|
|
11
|
+
key builders live here where both the producers (runner / evaluation route)
|
|
12
|
+
and the erasers (sqlite + supabase governance) can import them without
|
|
13
|
+
layering cycles.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
AGENT_SUCCESS_MARKER_PREFIX = "agent_success_group_eval"
|
|
19
|
+
GRADE_ON_DEMAND_CACHE_PREFIX = "grade_on_demand"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def build_agent_success_marker_key(org_id: str, user_id: str, session_id: str) -> str:
|
|
23
|
+
"""Build the agent-success "evaluated" marker key for a session.
|
|
24
|
+
|
|
25
|
+
Historical plain ``::`` join — kept byte-identical so existing markers
|
|
26
|
+
stay addressable.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
org_id (str): Organization ID.
|
|
30
|
+
user_id (str): Session owner.
|
|
31
|
+
session_id (str): Evaluated session.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
str: The marker key.
|
|
35
|
+
"""
|
|
36
|
+
return f"{AGENT_SUCCESS_MARKER_PREFIX}::{org_id}::{user_id}::{session_id}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def build_grade_on_demand_cache_key(
|
|
40
|
+
org_id: str, session_id: str, agent_version: str, evaluation_name: str
|
|
41
|
+
) -> str:
|
|
42
|
+
"""Build the grade-on-demand cache key (length-prefixed components).
|
|
43
|
+
|
|
44
|
+
Length-prefixing each free-form component keeps the join injective even
|
|
45
|
+
when a component contains the ``::`` delimiter.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
org_id (str): Organization ID.
|
|
49
|
+
session_id (str): Target session.
|
|
50
|
+
agent_version (str): Agent version filter.
|
|
51
|
+
evaluation_name (str): Evaluator/result namespace.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
str: The cache key.
|
|
55
|
+
"""
|
|
56
|
+
parts = "::".join(
|
|
57
|
+
f"{len(s)}:{s}" for s in (org_id, session_id, agent_version, evaluation_name)
|
|
58
|
+
)
|
|
59
|
+
return f"{GRADE_ON_DEMAND_CACHE_PREFIX}::{parts}"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def build_grade_on_demand_session_prefix(org_id: str, session_id: str) -> str:
|
|
63
|
+
"""Prefix matching every grade-on-demand cache key for one session.
|
|
64
|
+
|
|
65
|
+
Used by governance erasure to find a session's cache rows regardless of
|
|
66
|
+
agent_version / evaluation_name.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
org_id (str): Organization ID.
|
|
70
|
+
session_id (str): Target session.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
str: The per-session key prefix (ends with ``::``).
|
|
74
|
+
"""
|
|
75
|
+
return (
|
|
76
|
+
f"{GRADE_ON_DEMAND_CACHE_PREFIX}"
|
|
77
|
+
f"::{len(org_id)}:{org_id}::{len(session_id)}:{session_id}::"
|
|
78
|
+
)
|
package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py
CHANGED
|
@@ -161,6 +161,44 @@ class AgentPlaybookStoreMixin:
|
|
|
161
161
|
"""
|
|
162
162
|
raise NotImplementedError
|
|
163
163
|
|
|
164
|
+
@abstractmethod
|
|
165
|
+
def get_agent_playbooks_by_ids(
|
|
166
|
+
self,
|
|
167
|
+
agent_playbook_ids: list[int],
|
|
168
|
+
*,
|
|
169
|
+
status_filter: list[Status | None] | None = None,
|
|
170
|
+
playbook_status_filter: list[PlaybookStatus] | None = None,
|
|
171
|
+
include_inactive: bool = False,
|
|
172
|
+
) -> list[AgentPlaybook]:
|
|
173
|
+
"""Fetch agent playbooks in bulk with lifecycle filters.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
agent_playbook_ids (list[int]): Playbook ids to fetch. Empty list
|
|
177
|
+
returns ``[]`` without hitting storage.
|
|
178
|
+
status_filter (list[Status | None] | None): Lifecycle statuses to
|
|
179
|
+
include. ``None`` (default) means CURRENT only.
|
|
180
|
+
playbook_status_filter (list[PlaybookStatus] | None): Approval
|
|
181
|
+
statuses to include. ``None`` (default) means no approval filter.
|
|
182
|
+
include_inactive (bool): Return every matching row regardless of
|
|
183
|
+
lifecycle status *or* approval status. This is the *historical
|
|
184
|
+
resolution* mode (see ``RetrievedLearningEvaluator``): it answers
|
|
185
|
+
"what did this id point at", not "what is retrievable now", so an
|
|
186
|
+
archived or never-approved playbook that was actually served is
|
|
187
|
+
still returned. It is a strict superset of ``include_tombstones``
|
|
188
|
+
on ``get_agent_playbook_by_id``, which only unhides
|
|
189
|
+
MERGED/SUPERSEDED for lineage walks. The default preserves
|
|
190
|
+
retrieval behavior.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
list[AgentPlaybook]: Matching playbooks. Order is unspecified.
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
StorageError: If ``include_inactive`` is combined with an explicit
|
|
197
|
+
``status_filter`` or ``playbook_status_filter`` — the two are
|
|
198
|
+
contradictory.
|
|
199
|
+
"""
|
|
200
|
+
raise NotImplementedError
|
|
201
|
+
|
|
164
202
|
@abstractmethod
|
|
165
203
|
def delete_all_agent_playbooks(self) -> None:
|
|
166
204
|
"""Delete all agent playbooks from storage."""
|