claude-smart 0.2.46 → 0.2.48
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 +1 -1
- package/README.md +19 -11
- package/bin/claude-smart.js +290 -68
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +11 -10
- package/plugin/dashboard/app/layout.tsx +20 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +36 -2
- package/plugin/dashboard/package-lock.json +61 -390
- package/plugin/dashboard/package.json +2 -2
- package/plugin/opencode/dist/server.mjs +76 -2
- package/plugin/opencode/server.mts +79 -2
- package/plugin/pyproject.toml +6 -2
- package/plugin/scripts/smart-install.sh +7 -1
- package/plugin/src/claude_smart/cli.py +210 -22
- package/plugin/src/claude_smart/context_format.py +9 -9
- package/plugin/src/claude_smart/cs_cite.py +66 -19
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/.env.example +7 -0
- package/plugin/vendor/reflexio/README.md +3 -3
- package/plugin/vendor/reflexio/pyproject.toml +2 -1
- package/plugin/vendor/reflexio/reflexio/README.md +11 -6
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +1 -1
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +2 -2
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
- package/plugin/vendor/reflexio/reflexio/client/client.py +97 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +8 -0
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +23 -18
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +9 -8
- package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +16 -1
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +27 -16
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +27 -5
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +9 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +45 -3
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +37 -0
- package/plugin/vendor/reflexio/reflexio/server/README.md +38 -9
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +21 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +274 -3267
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +4 -3
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +16 -1
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/{_auth.py → auth.py} +2 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +62 -36
- package/plugin/vendor/reflexio/reflexio/server/deployment_profile.py +69 -0
- package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +424 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_json_extraction.py +249 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_structured_output.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +980 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_types.py +110 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +73 -1819
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +57 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/middleware.py +244 -0
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +14 -2
- package/plugin/vendor/reflexio/reflexio/server/rate_limit.py +79 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_common.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_metering.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/braintrust.py +129 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +210 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +549 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +259 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/playbooks.py +578 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/profiles.py +423 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/provenance.py +345 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/search.py +349 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +261 -0
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +5 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/__init__.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_batch_progress.py +298 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_config_filter.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +243 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +299 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_status_change.py +273 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +258 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +17 -1183
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +26 -41
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +232 -123
- package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +385 -78
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +9 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +121 -525
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_clustering.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_postprocessing.py +130 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_prompt_formatting.py +212 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +258 -69
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/publish_learning_worker.py +288 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +29 -13
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +43 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +10 -1167
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +58 -351
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +49 -19
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +452 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1 -2133
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +7 -1126
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +73 -33
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py +506 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_pending_tool_call_store.py +704 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_run_tool_dependency_store.py +123 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_audit.py +122 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +465 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_purge.py +387 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py +332 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +511 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +955 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +844 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +896 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +50 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +64 -370
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +0 -909
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py +86 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_pending_tool_call_store.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_run_tool_dependency_store.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_audit.py +29 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_erase_execution.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_purge.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_subject_barrier.py +49 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +73 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/{_profiles.py → profiles/_profile_store.py} +57 -86
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_search.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +153 -12
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +0 -84
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""System/meta/stats/operations route handlers (extracted from api.py, Tier3 A2)."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
from fastapi import (
|
|
10
|
+
APIRouter,
|
|
11
|
+
Depends,
|
|
12
|
+
HTTPException,
|
|
13
|
+
Request,
|
|
14
|
+
status,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from reflexio.models.api_schema.retriever_schema import (
|
|
18
|
+
GetDashboardStatsRequest,
|
|
19
|
+
GetDashboardStatsResponse,
|
|
20
|
+
StorageStatsRequest,
|
|
21
|
+
StorageStatsResponse,
|
|
22
|
+
)
|
|
23
|
+
from reflexio.models.api_schema.service_schemas import (
|
|
24
|
+
AdminInvalidateCacheRequest,
|
|
25
|
+
AdminInvalidateCacheResponse,
|
|
26
|
+
CancelOperationRequest,
|
|
27
|
+
CancelOperationResponse,
|
|
28
|
+
ClearUserDataRequest,
|
|
29
|
+
ClearUserDataResponse,
|
|
30
|
+
GetOperationStatusRequest,
|
|
31
|
+
GetOperationStatusResponse,
|
|
32
|
+
WhoamiResponse,
|
|
33
|
+
)
|
|
34
|
+
from reflexio.server.api_endpoints import (
|
|
35
|
+
account_api,
|
|
36
|
+
publisher_api,
|
|
37
|
+
)
|
|
38
|
+
from reflexio.server.auth import (
|
|
39
|
+
default_get_org_id,
|
|
40
|
+
)
|
|
41
|
+
from reflexio.server.cache import reflexio_cache
|
|
42
|
+
from reflexio.server.rate_limit import limiter
|
|
43
|
+
from reflexio.server.routes._common import _run_limited_api
|
|
44
|
+
|
|
45
|
+
logger = logging.getLogger(__name__)
|
|
46
|
+
router = APIRouter()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.get("/")
|
|
50
|
+
def root() -> dict[str, str]:
|
|
51
|
+
return {
|
|
52
|
+
"service": "Reflexio API",
|
|
53
|
+
"docs": "/docs",
|
|
54
|
+
"health": "/health",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@router.get("/health")
|
|
59
|
+
async def health_check() -> dict[str, str]:
|
|
60
|
+
"""Health check endpoint for ECS/container orchestration."""
|
|
61
|
+
return {"status": "healthy"}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@router.get(
|
|
65
|
+
"/api/whoami",
|
|
66
|
+
response_model=WhoamiResponse,
|
|
67
|
+
response_model_exclude_none=True,
|
|
68
|
+
)
|
|
69
|
+
def whoami_endpoint(
|
|
70
|
+
org_id: str = Depends(default_get_org_id),
|
|
71
|
+
) -> WhoamiResponse:
|
|
72
|
+
"""Return the caller's org and masked storage routing.
|
|
73
|
+
|
|
74
|
+
Powers ``reflexio status``. Safe to call unauthenticated in
|
|
75
|
+
self-host mode; the enterprise server wraps this in Bearer auth.
|
|
76
|
+
"""
|
|
77
|
+
return account_api.whoami(org_id=org_id)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@router.get(
|
|
81
|
+
"/api/storage_stats",
|
|
82
|
+
response_model=StorageStatsResponse,
|
|
83
|
+
response_model_exclude_none=True,
|
|
84
|
+
)
|
|
85
|
+
@limiter.limit("120/minute") # Rate limit for read operations
|
|
86
|
+
def storage_stats(
|
|
87
|
+
request: Request,
|
|
88
|
+
user_id: str,
|
|
89
|
+
org_id: str = Depends(default_get_org_id),
|
|
90
|
+
) -> StorageStatsResponse:
|
|
91
|
+
"""Return lightweight metadata about a user's profiles and playbooks.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
request (Request): The HTTP request object (for rate limiting)
|
|
95
|
+
user_id (str): Target user id, passed as a query parameter so this is
|
|
96
|
+
a cacheable, idempotent GET.
|
|
97
|
+
org_id (str): Organization ID
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
StorageStatsResponse: Counts and timestamp range for the user.
|
|
101
|
+
"""
|
|
102
|
+
return _run_limited_api(
|
|
103
|
+
org_id,
|
|
104
|
+
"search",
|
|
105
|
+
lambda: reflexio_cache.get_reflexio(org_id=org_id).storage_stats(
|
|
106
|
+
StorageStatsRequest(user_id=user_id)
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@router.post(
|
|
112
|
+
"/api/clear_user_data",
|
|
113
|
+
response_model=ClearUserDataResponse,
|
|
114
|
+
response_model_exclude_none=True,
|
|
115
|
+
)
|
|
116
|
+
@limiter.limit("10/minute")
|
|
117
|
+
def clear_user_data(
|
|
118
|
+
request: Request,
|
|
119
|
+
payload: ClearUserDataRequest,
|
|
120
|
+
org_id: str = Depends(default_get_org_id),
|
|
121
|
+
) -> ClearUserDataResponse:
|
|
122
|
+
"""Delete all rows scoped to a single ``user_id``.
|
|
123
|
+
|
|
124
|
+
Removes the user's interactions, user playbooks, profiles, and
|
|
125
|
+
requests. Does NOT touch ``agent_playbooks`` — they are
|
|
126
|
+
intentionally shared cross-project. Used by paired-protocol
|
|
127
|
+
harnesses (e.g. SWE-bench) to isolate per-task data on a shared
|
|
128
|
+
backend without one task's clear-all nuking another in-flight
|
|
129
|
+
task's rows.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
request (ClearUserDataRequest): Request containing the target user_id
|
|
133
|
+
org_id (str): Organization ID
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
ClearUserDataResponse: Response with per-entity deletion counts
|
|
137
|
+
"""
|
|
138
|
+
return publisher_api.clear_user_data(org_id=org_id, request=payload)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@router.post("/api/admin/cache/invalidate")
|
|
142
|
+
def admin_invalidate_cache(
|
|
143
|
+
payload: AdminInvalidateCacheRequest,
|
|
144
|
+
org_id: str = Depends(default_get_org_id),
|
|
145
|
+
) -> AdminInvalidateCacheResponse:
|
|
146
|
+
"""Explicitly evict the per-org Reflexio cache entry.
|
|
147
|
+
|
|
148
|
+
Necessary when the running config has been mutated through a
|
|
149
|
+
channel the server can't observe — e.g. another replica wrote to
|
|
150
|
+
the shared DB, or an operator hand-edited a self-host config file
|
|
151
|
+
on a backend that doesn't support cheap version probing. The
|
|
152
|
+
file-mtime check (Phase 1) and DB version check (Phase 3) cover
|
|
153
|
+
most cases automatically; this endpoint is the manual escape hatch.
|
|
154
|
+
|
|
155
|
+
Auth uses the same dependency as ``/api/set_config`` — callers
|
|
156
|
+
can only invalidate their own org's cache. If the request body
|
|
157
|
+
supplies ``org_id`` it must match the dep-resolved value;
|
|
158
|
+
cross-org invalidation is intentionally NOT exposed here.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
payload: Optional ``org_id`` (verification only — must match
|
|
162
|
+
the caller's authenticated org if provided).
|
|
163
|
+
org_id: Organization ID resolved by the auth layer.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
AdminInvalidateCacheResponse: ``invalidated`` is True iff an
|
|
167
|
+
entry was evicted (False is a successful no-op when nothing
|
|
168
|
+
was cached).
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
HTTPException: 403 when the body's ``org_id`` differs from the
|
|
172
|
+
caller's authenticated org.
|
|
173
|
+
"""
|
|
174
|
+
if payload.org_id is not None and payload.org_id != org_id:
|
|
175
|
+
raise HTTPException(
|
|
176
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
177
|
+
detail=(
|
|
178
|
+
"Cross-org cache invalidation is not supported; "
|
|
179
|
+
"omit org_id or pass your own."
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
invalidated = reflexio_cache.invalidate_reflexio_cache(org_id=org_id)
|
|
183
|
+
return AdminInvalidateCacheResponse(invalidated=invalidated, org_id=org_id)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@router.post(
|
|
187
|
+
"/api/get_dashboard_stats",
|
|
188
|
+
response_model=GetDashboardStatsResponse,
|
|
189
|
+
response_model_exclude_none=True,
|
|
190
|
+
)
|
|
191
|
+
@limiter.limit("30/minute")
|
|
192
|
+
def get_dashboard_stats(
|
|
193
|
+
request: Request,
|
|
194
|
+
payload: GetDashboardStatsRequest,
|
|
195
|
+
org_id: str = Depends(default_get_org_id),
|
|
196
|
+
) -> GetDashboardStatsResponse:
|
|
197
|
+
"""Get comprehensive dashboard statistics including counts and time-series data.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
request (GetDashboardStatsRequest): Request containing days_back and granularity
|
|
201
|
+
org_id (str): Organization ID
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
GetDashboardStatsResponse: Response containing dashboard statistics
|
|
205
|
+
"""
|
|
206
|
+
# Create Reflexio instance
|
|
207
|
+
reflexio = reflexio_cache.get_reflexio(org_id=org_id)
|
|
208
|
+
|
|
209
|
+
# Get dashboard stats using Reflexio's method
|
|
210
|
+
return reflexio.get_dashboard_stats(payload)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@router.get(
|
|
214
|
+
"/api/get_operation_status",
|
|
215
|
+
response_model=GetOperationStatusResponse,
|
|
216
|
+
response_model_exclude_none=True,
|
|
217
|
+
)
|
|
218
|
+
def get_operation_status_endpoint(
|
|
219
|
+
service_name: str = "profile_generation",
|
|
220
|
+
org_id: str = Depends(default_get_org_id),
|
|
221
|
+
) -> GetOperationStatusResponse:
|
|
222
|
+
"""Get the status of an operation (e.g., profile generation rerun or manual).
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
service_name (str): The service name to query. Defaults to "profile_generation"
|
|
226
|
+
org_id (str): Organization ID
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
GetOperationStatusResponse: Response containing operation status info
|
|
230
|
+
"""
|
|
231
|
+
# Create Reflexio instance
|
|
232
|
+
reflexio = reflexio_cache.get_reflexio(org_id=org_id)
|
|
233
|
+
|
|
234
|
+
# Get operation status
|
|
235
|
+
request = GetOperationStatusRequest(service_name=service_name)
|
|
236
|
+
return reflexio.get_operation_status(request)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@router.post(
|
|
240
|
+
"/api/cancel_operation",
|
|
241
|
+
response_model=CancelOperationResponse,
|
|
242
|
+
response_model_exclude_none=True,
|
|
243
|
+
)
|
|
244
|
+
@limiter.limit("10/minute")
|
|
245
|
+
def cancel_operation_endpoint(
|
|
246
|
+
request: Request,
|
|
247
|
+
payload: CancelOperationRequest,
|
|
248
|
+
org_id: str = Depends(default_get_org_id),
|
|
249
|
+
) -> CancelOperationResponse:
|
|
250
|
+
"""Cancel an in-progress operation (rerun or manual generation).
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
request (Request): The HTTP request object (for rate limiting)
|
|
254
|
+
payload (CancelOperationRequest): Request containing optional service_name
|
|
255
|
+
org_id (str): Organization ID
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
CancelOperationResponse: Response with list of services that were cancelled
|
|
259
|
+
"""
|
|
260
|
+
reflexio = reflexio_cache.get_reflexio(org_id=org_id)
|
|
261
|
+
return reflexio.cancel_operation(payload)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Shared thread-lifecycle base for the process-local polling schedulers.
|
|
2
|
+
|
|
3
|
+
Reflexio runs a family of background daemons — extraction resume, lineage GC,
|
|
4
|
+
and (in the enterprise layer) billing ship / lease / period-close / integrity /
|
|
5
|
+
audit / offline-tuner / governance-retention / invitation-reclamation. Every one
|
|
6
|
+
of them hand-rolled the SAME thread mechanics: a :class:`threading.Event`
|
|
7
|
+
stop-signal, a daemon :class:`threading.Thread` running a ``while not
|
|
8
|
+
stop.is_set()`` loop, and a ``.join(timeout)`` graceful stop.
|
|
9
|
+
|
|
10
|
+
:class:`ThreadedScheduler` captures ONLY those mechanics. Each scheduler keeps
|
|
11
|
+
its own poll interval, work body, gating, and log lines — supplied via a small
|
|
12
|
+
set of hooks so no observable behaviour changes:
|
|
13
|
+
|
|
14
|
+
- :meth:`_run_once` performs one tick (catching its own errors) and returns the
|
|
15
|
+
number of seconds to wait before the next tick. A scheduler that reads its
|
|
16
|
+
interval from config each tick keeps doing exactly that by returning the fresh
|
|
17
|
+
value.
|
|
18
|
+
- :meth:`_should_start` can veto startup (the enterprise schedulers whose
|
|
19
|
+
``start`` is a logged no-op when a feature gate is off).
|
|
20
|
+
- :meth:`_on_started` / :meth:`_on_stopped` emit each scheduler's own start/stop
|
|
21
|
+
log line (through its own module logger, so output is byte-identical).
|
|
22
|
+
|
|
23
|
+
A scheduler whose loop is materially different (e.g. a bounded-attempt retrier
|
|
24
|
+
that waits *before* each attempt and exits on first success) may override
|
|
25
|
+
:meth:`_run_loop` wholesale and still reuse :meth:`start` / :meth:`stop` /
|
|
26
|
+
:meth:`is_running`.
|
|
27
|
+
|
|
28
|
+
This module is an OSS public seam: it imports only the standard library and
|
|
29
|
+
nothing from ``reflexio_ext``.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import threading
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ThreadedScheduler:
|
|
38
|
+
"""Base for a process-local daemon that ticks on a background thread.
|
|
39
|
+
|
|
40
|
+
Owns the shared thread mechanics only: a stop :class:`threading.Event`, a
|
|
41
|
+
daemon thread, an idempotent :meth:`start`, and a join-with-timeout
|
|
42
|
+
:meth:`stop`. Subclasses supply the per-tick work + cadence via
|
|
43
|
+
:meth:`_run_once` (or override :meth:`_run_loop` for a bespoke loop shape).
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
thread_name (str): OS thread name for the daemon (aids debugging / logs).
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, *, thread_name: str) -> None:
|
|
50
|
+
self._thread_name = thread_name
|
|
51
|
+
self._stop_event = threading.Event()
|
|
52
|
+
self._thread: threading.Thread | None = None
|
|
53
|
+
|
|
54
|
+
def start(self) -> None:
|
|
55
|
+
"""Start the daemon thread, unless it is gated off or already running.
|
|
56
|
+
|
|
57
|
+
Calls :meth:`_should_start` first (a subclass gate that can log + veto),
|
|
58
|
+
then the idempotent alive-check, then spawns the thread and calls
|
|
59
|
+
:meth:`_on_started`.
|
|
60
|
+
"""
|
|
61
|
+
if not self._should_start():
|
|
62
|
+
return
|
|
63
|
+
if self._thread is not None and self._thread.is_alive():
|
|
64
|
+
return
|
|
65
|
+
self._stop_event.clear()
|
|
66
|
+
self._thread = threading.Thread(
|
|
67
|
+
target=self._run_loop,
|
|
68
|
+
name=self._thread_name,
|
|
69
|
+
daemon=True,
|
|
70
|
+
)
|
|
71
|
+
self._thread.start()
|
|
72
|
+
self._on_started()
|
|
73
|
+
|
|
74
|
+
def stop(self, *, timeout_seconds: float = 5.0) -> None:
|
|
75
|
+
"""Signal the loop to stop and join the thread.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
timeout_seconds (float): Max seconds to wait for the thread to exit.
|
|
79
|
+
"""
|
|
80
|
+
self._stop_event.set()
|
|
81
|
+
if self._thread is not None:
|
|
82
|
+
self._thread.join(timeout=timeout_seconds)
|
|
83
|
+
# Only drop the reference once the thread has actually exited. If the
|
|
84
|
+
# join timed out (a slow ``_run_once`` still running), keep it so
|
|
85
|
+
# ``is_running()`` stays true and a subsequent ``start()`` refuses to
|
|
86
|
+
# spawn a second loop while the first is alive.
|
|
87
|
+
if not self._thread.is_alive():
|
|
88
|
+
self._thread = None
|
|
89
|
+
self._on_stopped()
|
|
90
|
+
|
|
91
|
+
def is_running(self) -> bool:
|
|
92
|
+
"""Return whether the background thread exists and is alive.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
bool: True when the loop thread is running.
|
|
96
|
+
"""
|
|
97
|
+
return self._thread is not None and self._thread.is_alive()
|
|
98
|
+
|
|
99
|
+
# -- Overridable hooks ---------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def _should_start(self) -> bool:
|
|
102
|
+
"""Return whether :meth:`start` should spawn the thread (default True).
|
|
103
|
+
|
|
104
|
+
Override to gate startup behind a feature flag; the override owns any
|
|
105
|
+
"not starting" log line.
|
|
106
|
+
"""
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
def _on_started(self) -> None:
|
|
110
|
+
"""Hook run after the thread starts (default no-op); override to log."""
|
|
111
|
+
|
|
112
|
+
def _on_stopped(self) -> None:
|
|
113
|
+
"""Hook run after the thread joins (default no-op); override to log."""
|
|
114
|
+
|
|
115
|
+
def _run_once(self) -> float:
|
|
116
|
+
"""Run one tick and return the seconds to wait before the next.
|
|
117
|
+
|
|
118
|
+
Implementations MUST catch their own per-tick errors — a raise here would
|
|
119
|
+
kill the daemon thread. The returned value is passed straight to
|
|
120
|
+
``stop_event.wait`` as the inter-tick delay, so a subclass may compute a
|
|
121
|
+
fresh interval each tick (e.g. from config).
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
float: Seconds to wait before the next tick.
|
|
125
|
+
"""
|
|
126
|
+
raise NotImplementedError
|
|
127
|
+
|
|
128
|
+
def _run_loop(self) -> None:
|
|
129
|
+
"""Drive :meth:`_run_once` until stopped, waiting its returned interval."""
|
|
130
|
+
while not self._stop_event.is_set():
|
|
131
|
+
interval = self._run_once()
|
|
132
|
+
self._stop_event.wait(interval)
|
|
@@ -23,7 +23,8 @@ strings before deleting old import paths in the same PR.
|
|
|
23
23
|
| File | Purpose |
|
|
24
24
|
|------|---------|
|
|
25
25
|
| `generation_service.py` | `GenerationService` — saves interactions, runs profile + playbook generation in parallel (ThreadPoolExecutor), schedules deferred evaluation when `session_id` is present. |
|
|
26
|
-
| `
|
|
26
|
+
| `publish_learning_worker.py` | Deferred post-persist publish learning worker — queues async publish learning after durable interaction writes and requeues under publish limiter pressure. |
|
|
27
|
+
| `base_generation_service.py` + `base_generation/` | `BaseGenerationService` stable import surface plus mixins for batch progress, config filtering, extraction lifecycle, should-run prechecks, status transitions, and usage billing. Per-extractor timeout `EXTRACTOR_TIMEOUT_SECONDS = 300`. |
|
|
27
28
|
| `operation_state_utils.py` | `OperationStateManager` — all `_operation_state` access (progress, concurrency locks, extractor/aggregator bookmarks, cluster fingerprints, cancellation). |
|
|
28
29
|
| `extractor_config_utils.py`, `extractor_interaction_utils.py` | Filter extractors by source / `allow_manual_trigger` / names; per-extractor stride + window + bookmark handling. |
|
|
29
30
|
| `deduplication_utils.py`, `service_utils.py`, `embedding_text.py` | LLM dedup helpers (used by `ProfileConsolidator` + `PlaybookConsolidator`), message construction / JSON extraction / response logging, embedding text builders. |
|
|
@@ -52,6 +53,7 @@ strings before deleting old import paths in the same PR.
|
|
|
52
53
|
| `playbook_optimizer/` | Scenario-based playbook optimization: mature flat package with `optimizer.py`, `scheduler.py`, `rollout.py`, `judge.py`, `models.py`, `scenario_resolver.py`, `gepa_adapter.py`, and `assistant_webhook.py`. See [README](playbook_optimizer/README.md). |
|
|
53
54
|
| `braintrust/` | Braintrust export/sync: `service.py`, `client.py`, `_cron.py`, `_encryption.py`. |
|
|
54
55
|
| `lineage/` | Current-record resolution and tombstone GC: `resolve.py`, `gc_scheduler.py`. |
|
|
56
|
+
| `governance/` | Subject-reference and retention/barrier policy contracts: `config.py`, `service.py`, `subject_refs.py`. Storage and lineage code use these helpers to preserve governance constraints during writes, supersedes, and cleanup. |
|
|
55
57
|
| `pre_retrieval/` | `QueryReformulator` (`_query_reformulator.py`) + `DocumentExpander` (`_document_expander.py`) - query rewrite and doc expansion for recall. Compact by design; see [README](pre_retrieval/README.md). |
|
|
56
58
|
| `tagging/` | `TaggingService` (`service.py`) + deferred `tagging_scheduler.py` - post-generation profile/playbook tagging. Compact by design; see [README](tagging/README.md). |
|
|
57
59
|
| `unified_search_service.py` | `run_unified_search()` — two-phase parallel search across profiles / agent playbooks / user playbooks. |
|
|
@@ -61,7 +63,7 @@ strings before deleting old import paths in the same PR.
|
|
|
61
63
|
|
|
62
64
|
| Path | Purpose |
|
|
63
65
|
|------|---------|
|
|
64
|
-
| `storage/` | `storage_base/`
|
|
66
|
+
| `storage/` | `storage_base/` and `sqlite_storage/` keep legacy domain facades while focused subpackages own `profiles/`, `playbook/`, `agent_run/`, `governance/`, and SQLite `base/` helpers; plus `governance_validation.py` and `retention*.py`. Access via `request_context.storage` only. |
|
|
65
67
|
| `configurator/` | `DefaultConfigurator` — loads YAML config and creates the storage backend. |
|
|
66
68
|
|
|
67
69
|
## Key Rules
|
|
@@ -71,3 +73,4 @@ strings before deleting old import paths in the same PR.
|
|
|
71
73
|
- **ALWAYS use `LiteLLMClient`** for completions/embeddings and `request_context.prompt_manager.render_prompt(...)` for prompts — no hardcoded prompts, no direct OpenAI/Claude clients.
|
|
72
74
|
- **All `_operation_state` writes go through `OperationStateManager`** — don't touch the table directly (it backs locks, bookmarks, progress, and cancellation).
|
|
73
75
|
- **`tool_can_use` lives at root `Config`** — shared by playbook extraction and success evaluation, not per-service.
|
|
76
|
+
- **Preserve governance subject refs/barriers** — route validation through `services/governance/` and `storage/governance_validation.py`; do not bypass retention or subject-write checks in storage implementations.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Sub-mixins composed into ``BaseGenerationService`` (Tier-1b decomposition).
|
|
2
|
+
|
|
3
|
+
Each mixin holds a cohesive bucket of the ``BaseGenerationService`` behaviour and
|
|
4
|
+
is composed into the base via MRO in
|
|
5
|
+
``reflexio.server.services.base_generation_service``. The residual base keeps the
|
|
6
|
+
ABC skeleton (all abstract methods), ``__init__`` (per-run field inits), the ``run``
|
|
7
|
+
FIFO drain, ``_run_generation`` billing emit-ordering, and the lock/queue helpers.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from reflexio.server.services.base_generation._batch_progress import (
|
|
11
|
+
BatchProgressMixin,
|
|
12
|
+
)
|
|
13
|
+
from reflexio.server.services.base_generation._config_filter import (
|
|
14
|
+
ConfigFilterMixin,
|
|
15
|
+
)
|
|
16
|
+
from reflexio.server.services.base_generation._extraction_lifecycle import (
|
|
17
|
+
ExtractionRunLifecycleMixin,
|
|
18
|
+
)
|
|
19
|
+
from reflexio.server.services.base_generation._should_run import (
|
|
20
|
+
ShouldRunPrecheckMixin,
|
|
21
|
+
)
|
|
22
|
+
from reflexio.server.services.base_generation._status_change import (
|
|
23
|
+
StatusChangeMixin,
|
|
24
|
+
StatusChangeOperation,
|
|
25
|
+
)
|
|
26
|
+
from reflexio.server.services.base_generation._usage_billing import (
|
|
27
|
+
UsageBillingMixin,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"BatchProgressMixin",
|
|
32
|
+
"ConfigFilterMixin",
|
|
33
|
+
"ExtractionRunLifecycleMixin",
|
|
34
|
+
"ShouldRunPrecheckMixin",
|
|
35
|
+
"StatusChangeMixin",
|
|
36
|
+
"StatusChangeOperation",
|
|
37
|
+
"UsageBillingMixin",
|
|
38
|
+
]
|