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
|
@@ -7,8 +7,13 @@ Each Claude Code session gets one file at
|
|
|
7
7
|
- ``{"role": "Assistant", ...}`` — a finalized assistant turn
|
|
8
8
|
- ``{"role": "Assistant_tool", ...}`` — a single tool invocation, attached
|
|
9
9
|
to the next assistant turn at ``Stop`` time
|
|
10
|
-
- ``{"published_up_to": N}`` — high-water mark so Stop /
|
|
11
|
-
re-publish rows already sent to reflexio
|
|
10
|
+
- ``{"published_up_to": N, "request_id": "..."}`` — high-water mark so Stop /
|
|
11
|
+
SessionEnd don't re-publish rows already sent to reflexio; a confirmed
|
|
12
|
+
request ID preserves local learning lineage for the dashboard when available
|
|
13
|
+
- ``{"retrieved_learning_refs": [...]}`` — identity pairs shown to the agent,
|
|
14
|
+
attached by event order to the next Assistant turn
|
|
15
|
+
- ``{"publish_attempt": {"start": N, "end": M}}`` — frozen retry boundary for
|
|
16
|
+
one in-flight publish
|
|
12
17
|
|
|
13
18
|
The buffer exists for offline resilience: when reflexio is unreachable,
|
|
14
19
|
Stop appends without publishing and the next successful hook drains.
|
|
@@ -19,8 +24,9 @@ from __future__ import annotations
|
|
|
19
24
|
import json
|
|
20
25
|
import logging
|
|
21
26
|
import os
|
|
27
|
+
from collections.abc import Iterable
|
|
22
28
|
from pathlib import Path
|
|
23
|
-
from typing import Any
|
|
29
|
+
from typing import Any
|
|
24
30
|
|
|
25
31
|
try:
|
|
26
32
|
import fcntl # POSIX only — Windows hooks fall back to append-without-lock.
|
|
@@ -34,7 +40,12 @@ _DEFAULT_STATE_DIR = Path.home() / ".claude-smart" / "sessions"
|
|
|
34
40
|
|
|
35
41
|
_TOOL_DATA_FIELD_MAX_LEN = 256
|
|
36
42
|
|
|
37
|
-
_VALID_CITATION_KINDS = frozenset(
|
|
43
|
+
_VALID_CITATION_KINDS = frozenset(
|
|
44
|
+
{"playbook", "profile", "user_playbook", "agent_playbook"}
|
|
45
|
+
)
|
|
46
|
+
_VALID_RETRIEVED_PLAYBOOK_KINDS = frozenset({"user_playbook", "agent_playbook"})
|
|
47
|
+
_VALID_RETRIEVED_KINDS = _VALID_RETRIEVED_PLAYBOOK_KINDS | {"profile"}
|
|
48
|
+
_RETRIEVED_LEARNINGS_PUBLISH_CAP = 1000
|
|
38
49
|
|
|
39
50
|
|
|
40
51
|
def _truncate_tool_data_field(value: Any) -> Any:
|
|
@@ -81,17 +92,37 @@ def injected_path(session_id: str) -> Path:
|
|
|
81
92
|
return state_dir() / f"{session_id}.injected.jsonl"
|
|
82
93
|
|
|
83
94
|
|
|
95
|
+
def _normalize_registry_ref(entry: dict[str, Any]) -> dict[str, str] | None:
|
|
96
|
+
"""Return the identity pair carried by one citation-registry entry."""
|
|
97
|
+
|
|
98
|
+
learning_id = entry.get("real_id")
|
|
99
|
+
kind = entry.get("kind")
|
|
100
|
+
if not isinstance(learning_id, str) or not learning_id:
|
|
101
|
+
return None
|
|
102
|
+
if kind == "profile":
|
|
103
|
+
wire_kind = "profile"
|
|
104
|
+
elif (
|
|
105
|
+
kind == "playbook"
|
|
106
|
+
and entry.get("source_kind") in _VALID_RETRIEVED_PLAYBOOK_KINDS
|
|
107
|
+
):
|
|
108
|
+
wire_kind = entry["source_kind"]
|
|
109
|
+
else:
|
|
110
|
+
return None
|
|
111
|
+
return {"kind": wire_kind, "learning_id": learning_id}
|
|
112
|
+
|
|
113
|
+
|
|
84
114
|
def append_injected(session_id: str, entries: Iterable[dict[str, Any]]) -> None:
|
|
85
|
-
"""
|
|
115
|
+
"""Record injected items for citations and the next Assistant publish.
|
|
86
116
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Silently no-ops when ``entries`` is empty.
|
|
117
|
+
The citation sidecar keeps the display metadata used by Stop. The ordered
|
|
118
|
+
session event stores identity pairs only, so publishing never has to join
|
|
119
|
+
two files or infer event order from timestamps.
|
|
91
120
|
"""
|
|
121
|
+
|
|
92
122
|
records = list(entries)
|
|
93
123
|
if not records:
|
|
94
124
|
return
|
|
125
|
+
|
|
95
126
|
path = injected_path(session_id)
|
|
96
127
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
97
128
|
with path.open("a", encoding="utf-8") as fh:
|
|
@@ -100,8 +131,22 @@ def append_injected(session_id: str, entries: Iterable[dict[str, Any]]) -> None:
|
|
|
100
131
|
fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
|
|
101
132
|
except OSError as exc:
|
|
102
133
|
_LOGGER.debug("flock failed on %s: %s", path, exc)
|
|
103
|
-
for
|
|
104
|
-
fh.write(json.dumps(
|
|
134
|
+
for record in records:
|
|
135
|
+
fh.write(json.dumps(record, ensure_ascii=False) + "\n")
|
|
136
|
+
|
|
137
|
+
refs: list[dict[str, str]] = []
|
|
138
|
+
seen: set[tuple[str, str]] = set()
|
|
139
|
+
for record in records:
|
|
140
|
+
ref = _normalize_registry_ref(record)
|
|
141
|
+
if ref is None:
|
|
142
|
+
continue
|
|
143
|
+
key = (ref["kind"], ref["learning_id"])
|
|
144
|
+
if key in seen:
|
|
145
|
+
continue
|
|
146
|
+
seen.add(key)
|
|
147
|
+
refs.append(ref)
|
|
148
|
+
if refs:
|
|
149
|
+
append(session_id, {"retrieved_learning_refs": refs})
|
|
105
150
|
|
|
106
151
|
|
|
107
152
|
def read_injected(session_id: str) -> dict[str, dict[str, Any]]:
|
|
@@ -111,6 +156,7 @@ def read_injected(session_id: str) -> dict[str, dict[str, Any]]:
|
|
|
111
156
|
(identical content produces the same hash-derived id, so the extra
|
|
112
157
|
record only refreshes metadata).
|
|
113
158
|
"""
|
|
159
|
+
|
|
114
160
|
path = injected_path(session_id)
|
|
115
161
|
if not path.exists():
|
|
116
162
|
return {}
|
|
@@ -206,13 +252,18 @@ def _to_wire_citations(cited_items: Any) -> list[dict[str, str]]:
|
|
|
206
252
|
kind = item.get("kind")
|
|
207
253
|
if not isinstance(real_id, str) or not real_id:
|
|
208
254
|
continue
|
|
209
|
-
|
|
255
|
+
wire_kind = kind
|
|
256
|
+
if kind == "playbook":
|
|
257
|
+
source_kind = item.get("source_kind")
|
|
258
|
+
if source_kind in {"user_playbook", "agent_playbook"}:
|
|
259
|
+
wire_kind = source_kind
|
|
260
|
+
if wire_kind not in _VALID_CITATION_KINDS:
|
|
210
261
|
continue
|
|
211
262
|
tag = item.get("id")
|
|
212
263
|
title = item.get("title")
|
|
213
264
|
out.append(
|
|
214
265
|
{
|
|
215
|
-
"kind":
|
|
266
|
+
"kind": wire_kind,
|
|
216
267
|
"real_id": real_id,
|
|
217
268
|
"tag": tag if isinstance(tag, str) else "",
|
|
218
269
|
"title": title if isinstance(title, str) else "",
|
|
@@ -221,44 +272,127 @@ def _to_wire_citations(cited_items: Any) -> list[dict[str, str]]:
|
|
|
221
272
|
return out
|
|
222
273
|
|
|
223
274
|
|
|
275
|
+
def published_record_offset(records: list[dict[str, Any]]) -> int:
|
|
276
|
+
"""Return the highest valid publish watermark in the session buffer."""
|
|
277
|
+
|
|
278
|
+
limit = len(records)
|
|
279
|
+
return max(
|
|
280
|
+
(
|
|
281
|
+
value
|
|
282
|
+
for record in records
|
|
283
|
+
if isinstance((value := record.get("published_up_to")), int)
|
|
284
|
+
and 0 <= value <= limit
|
|
285
|
+
),
|
|
286
|
+
default=0,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def pending_publish_end(
|
|
291
|
+
records: list[dict[str, Any]], published: int
|
|
292
|
+
) -> int | None:
|
|
293
|
+
"""Return the frozen end of the current publish attempt, if any."""
|
|
294
|
+
|
|
295
|
+
for marker_index, record in enumerate(records[published:], start=published):
|
|
296
|
+
attempt = record.get("publish_attempt")
|
|
297
|
+
if not isinstance(attempt, dict):
|
|
298
|
+
continue
|
|
299
|
+
start = attempt.get("start")
|
|
300
|
+
end = attempt.get("end")
|
|
301
|
+
if (
|
|
302
|
+
start == published
|
|
303
|
+
and isinstance(end, int)
|
|
304
|
+
and published < end <= marker_index
|
|
305
|
+
):
|
|
306
|
+
_, interactions = unpublished_slice(
|
|
307
|
+
records[:end], published_override=published
|
|
308
|
+
)
|
|
309
|
+
if interactions:
|
|
310
|
+
return end
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _wire_retrieved_ref(value: Any) -> dict[str, str] | None:
|
|
315
|
+
"""Validate one identity pair read from the ordered session buffer."""
|
|
316
|
+
|
|
317
|
+
if not isinstance(value, dict):
|
|
318
|
+
return None
|
|
319
|
+
kind = value.get("kind")
|
|
320
|
+
learning_id = value.get("learning_id")
|
|
321
|
+
if kind not in _VALID_RETRIEVED_KINDS:
|
|
322
|
+
return None
|
|
323
|
+
if not isinstance(learning_id, str) or not learning_id:
|
|
324
|
+
return None
|
|
325
|
+
return {"kind": kind, "learning_id": learning_id}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def safe_publish_end(records: list[dict[str, Any]], published: int) -> int:
|
|
329
|
+
"""Stop before identity refs that do not yet have an Assistant target."""
|
|
330
|
+
|
|
331
|
+
unmatched_start: int | None = None
|
|
332
|
+
for index, record in enumerate(records[published:], start=published):
|
|
333
|
+
refs = record.get("retrieved_learning_refs")
|
|
334
|
+
if (
|
|
335
|
+
unmatched_start is None
|
|
336
|
+
and isinstance(refs, list)
|
|
337
|
+
and any(_wire_retrieved_ref(value) is not None for value in refs)
|
|
338
|
+
):
|
|
339
|
+
unmatched_start = index
|
|
340
|
+
if record.get("role") == "Assistant":
|
|
341
|
+
unmatched_start = None
|
|
342
|
+
return len(records) if unmatched_start is None else unmatched_start
|
|
343
|
+
|
|
344
|
+
|
|
224
345
|
def unpublished_slice(
|
|
225
346
|
records: Iterable[dict[str, Any]],
|
|
347
|
+
*,
|
|
348
|
+
published_override: int | None = None,
|
|
226
349
|
) -> tuple[int, list[dict[str, Any]]]:
|
|
227
|
-
"""
|
|
350
|
+
"""Return the current watermark and wire-ready unpublished interactions.
|
|
228
351
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
turn's ``tools_used``.
|
|
233
|
-
|
|
234
|
-
Returns:
|
|
235
|
-
tuple[int, list[dict]]: ``(published_up_to, interactions)``. The
|
|
236
|
-
integer is the watermark after which all turns are unpublished;
|
|
237
|
-
the list is formatted for ``InteractionData`` construction.
|
|
352
|
+
Metadata records are interpreted by their numeric boundary rather than
|
|
353
|
+
their physical position in the append-only file. This preserves turns that
|
|
354
|
+
arrive while an earlier network request is in flight.
|
|
238
355
|
"""
|
|
239
|
-
|
|
356
|
+
|
|
357
|
+
buffered = list(records)
|
|
358
|
+
if published_override is None:
|
|
359
|
+
published = published_record_offset(buffered)
|
|
360
|
+
elif 0 <= published_override <= len(buffered):
|
|
361
|
+
published = published_override
|
|
362
|
+
else:
|
|
363
|
+
raise ValueError("published_override is outside the supplied record range")
|
|
240
364
|
pending_tools: list[dict[str, Any]] = []
|
|
365
|
+
pending_refs: list[dict[str, str]] = []
|
|
241
366
|
turns: list[dict[str, Any]] = []
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
turns = []
|
|
367
|
+
attached_total = skipped = truncated = 0
|
|
368
|
+
|
|
369
|
+
for index, record in enumerate(buffered):
|
|
370
|
+
if index < published:
|
|
247
371
|
continue
|
|
248
|
-
|
|
372
|
+
|
|
373
|
+
refs = record.get("retrieved_learning_refs")
|
|
374
|
+
if "role" not in record and isinstance(refs, list):
|
|
375
|
+
for value in refs:
|
|
376
|
+
ref = _wire_retrieved_ref(value)
|
|
377
|
+
if ref is None:
|
|
378
|
+
skipped += 1
|
|
379
|
+
else:
|
|
380
|
+
pending_refs.append(ref)
|
|
249
381
|
continue
|
|
250
|
-
|
|
382
|
+
|
|
383
|
+
role = record.get("role")
|
|
251
384
|
if role == "Assistant_tool":
|
|
252
|
-
tool_input =
|
|
253
|
-
tool_output =
|
|
385
|
+
tool_input = record.get("tool_input") or {}
|
|
386
|
+
tool_output = record.get("tool_output") or ""
|
|
254
387
|
tool_entry: dict[str, Any] = {
|
|
255
|
-
"tool_name":
|
|
256
|
-
"status":
|
|
388
|
+
"tool_name": record.get("tool_name", ""),
|
|
389
|
+
"status": record.get("status", "success"),
|
|
257
390
|
}
|
|
258
391
|
tool_data: dict[str, Any] = {}
|
|
259
392
|
if tool_input:
|
|
260
393
|
tool_data["input"] = {
|
|
261
|
-
|
|
394
|
+
key: _truncate_tool_data_field(value)
|
|
395
|
+
for key, value in tool_input.items()
|
|
262
396
|
}
|
|
263
397
|
if tool_output:
|
|
264
398
|
tool_data["output"] = _truncate_tool_data_field(tool_output)
|
|
@@ -266,20 +400,45 @@ def unpublished_slice(
|
|
|
266
400
|
tool_entry["tool_data"] = tool_data
|
|
267
401
|
pending_tools.append(tool_entry)
|
|
268
402
|
continue
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
403
|
+
|
|
404
|
+
if role not in {"User", "Assistant"}:
|
|
405
|
+
continue
|
|
406
|
+
|
|
407
|
+
turn = {
|
|
408
|
+
key: value
|
|
409
|
+
for key, value in record.items()
|
|
410
|
+
if key not in {"role", "ts", "cited_items", "host"}
|
|
411
|
+
}
|
|
412
|
+
turn["role"] = role
|
|
413
|
+
if role == "Assistant":
|
|
414
|
+
citations = _to_wire_citations(record.get("cited_items"))
|
|
415
|
+
if citations:
|
|
416
|
+
turn["citations"] = citations
|
|
417
|
+
if pending_tools:
|
|
418
|
+
turn["tools_used"] = pending_tools
|
|
419
|
+
pending_tools = []
|
|
420
|
+
|
|
421
|
+
retrieved: list[dict[str, str]] = []
|
|
422
|
+
seen: set[tuple[str, str]] = set()
|
|
423
|
+
for ref in pending_refs:
|
|
424
|
+
key = (ref["kind"], ref["learning_id"])
|
|
425
|
+
if key in seen:
|
|
426
|
+
continue
|
|
427
|
+
seen.add(key)
|
|
428
|
+
if attached_total >= _RETRIEVED_LEARNINGS_PUBLISH_CAP:
|
|
429
|
+
truncated += 1
|
|
430
|
+
continue
|
|
431
|
+
retrieved.append(ref)
|
|
432
|
+
attached_total += 1
|
|
433
|
+
pending_refs = []
|
|
434
|
+
if retrieved:
|
|
435
|
+
turn["retrieved_learnings"] = retrieved
|
|
436
|
+
turns.append(turn)
|
|
437
|
+
|
|
438
|
+
if skipped:
|
|
439
|
+
_LOGGER.debug("Skipped %d malformed retrieved-learning refs", skipped)
|
|
440
|
+
if truncated:
|
|
441
|
+
_LOGGER.warning(
|
|
442
|
+
"Dropped %d retrieved-learning refs at the publish request cap", truncated
|
|
443
|
+
)
|
|
285
444
|
return published, turns
|
package/plugin/uv.lock
CHANGED
|
@@ -476,7 +476,7 @@ wheels = [
|
|
|
476
476
|
|
|
477
477
|
[[package]]
|
|
478
478
|
name = "claude-smart"
|
|
479
|
-
version = "0.2.
|
|
479
|
+
version = "0.2.50"
|
|
480
480
|
source = { editable = "." }
|
|
481
481
|
dependencies = [
|
|
482
482
|
{ name = "chromadb" },
|
|
@@ -494,7 +494,7 @@ dev = [
|
|
|
494
494
|
requires-dist = [
|
|
495
495
|
{ name = "chromadb", specifier = ">=0.5" },
|
|
496
496
|
{ name = "einops", specifier = ">=0.8.0" },
|
|
497
|
-
{ name = "reflexio-ai", specifier = ">=0.2.
|
|
497
|
+
{ name = "reflexio-ai", specifier = ">=0.2.28" },
|
|
498
498
|
{ name = "sqlite-vec", specifier = ">=0.1.6" },
|
|
499
499
|
]
|
|
500
500
|
|
|
@@ -2760,7 +2760,7 @@ wheels = [
|
|
|
2760
2760
|
|
|
2761
2761
|
[[package]]
|
|
2762
2762
|
name = "reflexio-ai"
|
|
2763
|
-
version = "0.2.
|
|
2763
|
+
version = "0.2.28"
|
|
2764
2764
|
source = { registry = "https://pypi.org/simple" }
|
|
2765
2765
|
dependencies = [
|
|
2766
2766
|
{ name = "aiohttp" },
|
|
@@ -2800,9 +2800,9 @@ dependencies = [
|
|
|
2800
2800
|
{ name = "websocket-client" },
|
|
2801
2801
|
{ name = "xlsxwriter" },
|
|
2802
2802
|
]
|
|
2803
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
2803
|
+
sdist = { url = "https://files.pythonhosted.org/packages/2a/e6/a59fc40136b37ea3e9b6878b600ef1f405c1baf7d1431a07e55c8e49c14e/reflexio_ai-0.2.28.tar.gz", hash = "sha256:49a35abf714ae293392277b278b895cde21ca50c14f10c97cbcf27c307e49fae", size = 1155554, upload-time = "2026-07-04T07:01:02.207Z" }
|
|
2804
2804
|
wheels = [
|
|
2805
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2805
|
+
{ url = "https://files.pythonhosted.org/packages/96/a6/c842d5e525f0f561fcd11ecf6543c9c52b3f4b665dbc2a8fe94adce9be1b/reflexio_ai-0.2.28-py3-none-any.whl", hash = "sha256:177e3a740afa2acb5a2a14a1e648c32c2d9e71ef9ff79b8ab2caec7e24ff5d88", size = 1558983, upload-time = "2026-07-04T07:01:00.69Z" },
|
|
2806
2806
|
]
|
|
2807
2807
|
|
|
2808
2808
|
[[package]]
|
|
@@ -77,6 +77,19 @@ REFLEXIO_PUBLISH_LEARNING_WORKERS=
|
|
|
77
77
|
# Inactivity delay in seconds before a quiet session is evaluated by agent-success
|
|
78
78
|
# evaluation. (optional; default 600 = 10 minutes)
|
|
79
79
|
GROUP_EVALUATION_DELAY_SECONDS=600
|
|
80
|
+
# Opt-in background sweep that re-embeds interactions whose embedding failed at
|
|
81
|
+
# ingest (empty vector, no vector row -> invisible to vector/hybrid search). Runs
|
|
82
|
+
# per-org on the shared lineage-GC scheduler cadence. Off by default so the GC
|
|
83
|
+
# scheduler's start conditions are unchanged unless you opt in.
|
|
84
|
+
# options: true | false (optional; default false)
|
|
85
|
+
REFLEXIO_MISSING_VECTOR_BACKFILL_ENABLED=false
|
|
86
|
+
# Max interactions re-embedded per org per sweep tick (bounds embedding cost and
|
|
87
|
+
# log volume). (optional; default 200; values <= 0 fall back to the default)
|
|
88
|
+
REFLEXIO_MISSING_VECTOR_BACKFILL_CAP=200
|
|
89
|
+
# Bounded per-tick org fan-out width for the lineage-GC / expiry-reclamation
|
|
90
|
+
# scheduler. SQLite storage is pinned to serial (1) regardless of this value.
|
|
91
|
+
# (optional; default 8; non-numeric or <=0 falls back to default)
|
|
92
|
+
REFLEXIO_SCHEDULER_ORG_WORKERS=
|
|
80
93
|
|
|
81
94
|
# =============================================================================
|
|
82
95
|
# 5. TESTING & LOGGING
|
|
@@ -56,8 +56,9 @@ Description: Python SDK for interacting with Reflexio API remotely
|
|
|
56
56
|
Remote API client for applications to:
|
|
57
57
|
1. **Publish interactions** - Send user interactions to server for processing
|
|
58
58
|
2. **Search/retrieve data** - Query profiles, interactions, playbooks, evaluations, and context
|
|
59
|
-
3. **
|
|
60
|
-
4. **
|
|
59
|
+
3. **Track deferred learning** - Poll `get_learning_status(request_id)` after `publish_interaction(..., wait_for_response=False)` queues extraction
|
|
60
|
+
4. **Manage profiles/playbooks** - Delete, regenerate, and update status where supported by API endpoints
|
|
61
|
+
5. **Configure** - Set/get organization configuration
|
|
61
62
|
|
|
62
63
|
#### Architecture Pattern
|
|
63
64
|
Async HTTP client wrapping typed models from `models/api_schema/`. Automatically handles authentication via Bearer tokens.
|
|
@@ -123,6 +124,7 @@ client (Python SDK)
|
|
|
123
124
|
-> services/generation_service.py (orchestrator)
|
|
124
125
|
├─> services/profile/ -> storage (BaseStorage)
|
|
125
126
|
├─> services/playbook/ (playbook extraction) -> storage (BaseStorage)
|
|
127
|
+
├─> services/durable_learning/ -> learning_jobs queue -> deferred extraction
|
|
126
128
|
└─> services/agent_success_evaluation/ -> storage (BaseStorage)
|
|
127
129
|
```
|
|
128
130
|
|
|
@@ -138,6 +140,7 @@ client (Python SDK)
|
|
|
138
140
|
- `profile/` - Profile extraction & updates
|
|
139
141
|
- `playbook/` - Playbook extraction, consolidation, and aggregation
|
|
140
142
|
- `agent_success_evaluation/` - Success evaluation
|
|
143
|
+
- `durable_learning/` - Claim/drain durable `learning_jobs` for deferred extraction when `REFLEXIO_DURABLE_LEARNING_QUEUE` is enabled
|
|
141
144
|
- `reflection/` - Post-horizon reflection extraction
|
|
142
145
|
- `extraction/` - Resumable async extraction agent infrastructure
|
|
143
146
|
- `shadow_comparison/` - Per-turn regular vs shadow verdict judge
|
|
@@ -146,9 +149,10 @@ client (Python SDK)
|
|
|
146
149
|
- `braintrust/` - Braintrust eval export/sync support
|
|
147
150
|
- `lineage/` - Resolve current records and schedule tombstone garbage collection for superseded profile/playbook rows
|
|
148
151
|
- `governance/` - Subject-reference contracts and retention/barrier helpers used by storage and lineage
|
|
149
|
-
- `storage/` - Abstract layer (SQLite prod, LocalJSON test) with governance-aware write validation
|
|
152
|
+
- `storage/` - Abstract layer (SQLite prod, LocalJSON test) with governance-aware write validation and durable `learning_jobs` contracts
|
|
150
153
|
- `pre_retrieval/` - Query rewriting and document expansion helpers
|
|
151
154
|
- `configurator/` - YAML config loader
|
|
155
|
+
- **`billing_meter.py`**: OSS usage-event facade for learning/search metering; keep imports function-local at call sites so enterprise emitters remain optional
|
|
152
156
|
- **`site_var/`**: Global settings singleton
|
|
153
157
|
|
|
154
158
|
### Architecture Patterns
|
|
@@ -5,6 +5,13 @@ try:
|
|
|
5
5
|
except PackageNotFoundError:
|
|
6
6
|
__version__ = "0.0.0-dev"
|
|
7
7
|
|
|
8
|
+
from reflexio.models.api_schema.eval_overview_schema import (
|
|
9
|
+
GradeOnDemandRequest,
|
|
10
|
+
GradeOnDemandResponse,
|
|
11
|
+
RegenerateRequest,
|
|
12
|
+
RegenerateStartResponse,
|
|
13
|
+
RegenerateStatusResponse,
|
|
14
|
+
)
|
|
8
15
|
from reflexio.models.api_schema.retriever_schema import (
|
|
9
16
|
ConversationTurn,
|
|
10
17
|
GetAgentPlaybooksViewResponse,
|
|
@@ -125,6 +132,8 @@ __all__ = [
|
|
|
125
132
|
"DeleteProfilesByIdsRequest",
|
|
126
133
|
"DeleteAgentPlaybooksByIdsRequest",
|
|
127
134
|
"DeleteUserPlaybooksByIdsRequest",
|
|
135
|
+
"GradeOnDemandRequest",
|
|
136
|
+
"RegenerateRequest",
|
|
128
137
|
# Response types (internal)
|
|
129
138
|
"PublishUserInteractionResponse",
|
|
130
139
|
"DeleteUserProfileResponse",
|
|
@@ -135,6 +144,9 @@ __all__ = [
|
|
|
135
144
|
"SearchInteractionResponse",
|
|
136
145
|
"SearchUserProfileResponse",
|
|
137
146
|
"BulkDeleteResponse",
|
|
147
|
+
"GradeOnDemandResponse",
|
|
148
|
+
"RegenerateStartResponse",
|
|
149
|
+
"RegenerateStatusResponse",
|
|
138
150
|
# View response types (user-facing)
|
|
139
151
|
"GetInteractionsViewResponse",
|
|
140
152
|
"GetProfilesViewResponse",
|
|
@@ -78,6 +78,9 @@ When `REFLEXIO_EMBEDDING_PROVIDER=local_service` or
|
|
|
78
78
|
`local/nomic-embed-text-v1.5`, and `local/minilm-l6-v2`. Local embedding
|
|
79
79
|
requests allow extra time for model cold start; override with
|
|
80
80
|
`REFLEXIO_EMBEDDING_SERVICE_TIMEOUT_MS` when needed.
|
|
81
|
+
MiniLM cache corruption is retried once automatically. If recovery still fails,
|
|
82
|
+
delete the cache directory named in the error message, restart Reflexio, and
|
|
83
|
+
retry local embedding.
|
|
81
84
|
|
|
82
85
|
## Publishing interactions
|
|
83
86
|
|