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.
Files changed (170) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +19 -11
  3. package/bin/claude-smart.js +290 -68
  4. package/package.json +1 -1
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +11 -10
  8. package/plugin/dashboard/app/layout.tsx +20 -0
  9. package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +36 -2
  10. package/plugin/dashboard/package-lock.json +61 -390
  11. package/plugin/dashboard/package.json +2 -2
  12. package/plugin/opencode/dist/server.mjs +76 -2
  13. package/plugin/opencode/server.mts +79 -2
  14. package/plugin/pyproject.toml +6 -2
  15. package/plugin/scripts/smart-install.sh +7 -1
  16. package/plugin/src/claude_smart/cli.py +210 -22
  17. package/plugin/src/claude_smart/context_format.py +9 -9
  18. package/plugin/src/claude_smart/cs_cite.py +66 -19
  19. package/plugin/uv.lock +5 -5
  20. package/plugin/vendor/reflexio/.env.example +7 -0
  21. package/plugin/vendor/reflexio/README.md +3 -3
  22. package/plugin/vendor/reflexio/pyproject.toml +2 -1
  23. package/plugin/vendor/reflexio/reflexio/README.md +11 -6
  24. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +1 -1
  25. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +2 -2
  26. package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
  27. package/plugin/vendor/reflexio/reflexio/client/client.py +97 -0
  28. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +8 -0
  29. package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
  30. package/plugin/vendor/reflexio/reflexio/lib/_config.py +23 -18
  31. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +9 -8
  32. package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +16 -1
  33. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +27 -16
  34. package/plugin/vendor/reflexio/reflexio/lib/_search.py +27 -5
  35. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +9 -0
  36. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
  37. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +1 -0
  38. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
  39. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +45 -3
  40. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +37 -0
  41. package/plugin/vendor/reflexio/reflexio/server/README.md +38 -9
  42. package/plugin/vendor/reflexio/reflexio/server/__init__.py +21 -2
  43. package/plugin/vendor/reflexio/reflexio/server/api.py +274 -3267
  44. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +4 -3
  45. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +16 -1
  46. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +1 -1
  47. package/plugin/vendor/reflexio/reflexio/server/{_auth.py → auth.py} +2 -0
  48. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +62 -36
  49. package/plugin/vendor/reflexio/reflexio/server/deployment_profile.py +69 -0
  50. package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
  51. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +424 -0
  52. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_json_extraction.py +249 -0
  53. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_structured_output.py +195 -0
  54. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +152 -0
  55. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +980 -0
  56. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_types.py +110 -0
  57. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +73 -1819
  58. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
  59. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +57 -5
  60. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
  61. package/plugin/vendor/reflexio/reflexio/server/middleware.py +244 -0
  62. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +9 -1
  63. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +14 -2
  64. package/plugin/vendor/reflexio/reflexio/server/rate_limit.py +79 -0
  65. package/plugin/vendor/reflexio/reflexio/server/routes/__init__.py +6 -0
  66. package/plugin/vendor/reflexio/reflexio/server/routes/_common.py +25 -0
  67. package/plugin/vendor/reflexio/reflexio/server/routes/_metering.py +98 -0
  68. package/plugin/vendor/reflexio/reflexio/server/routes/braintrust.py +129 -0
  69. package/plugin/vendor/reflexio/reflexio/server/routes/config.py +210 -0
  70. package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +549 -0
  71. package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +259 -0
  72. package/plugin/vendor/reflexio/reflexio/server/routes/playbooks.py +578 -0
  73. package/plugin/vendor/reflexio/reflexio/server/routes/profiles.py +423 -0
  74. package/plugin/vendor/reflexio/reflexio/server/routes/provenance.py +345 -0
  75. package/plugin/vendor/reflexio/reflexio/server/routes/search.py +349 -0
  76. package/plugin/vendor/reflexio/reflexio/server/routes/system.py +261 -0
  77. package/plugin/vendor/reflexio/reflexio/server/scheduling.py +132 -0
  78. package/plugin/vendor/reflexio/reflexio/server/services/README.md +5 -2
  79. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/__init__.py +38 -0
  80. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_batch_progress.py +298 -0
  81. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_config_filter.py +152 -0
  82. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +243 -0
  83. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +299 -0
  84. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_status_change.py +273 -0
  85. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +258 -0
  86. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +17 -1183
  87. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +8 -1
  88. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +26 -41
  89. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +8 -0
  90. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +232 -123
  91. package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
  92. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
  93. package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
  94. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +385 -78
  95. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +9 -1
  96. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
  97. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +121 -525
  98. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_clustering.py +184 -0
  99. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_postprocessing.py +130 -0
  100. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_prompt_formatting.py +212 -0
  101. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +258 -69
  102. package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +9 -9
  103. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +3 -2
  104. package/plugin/vendor/reflexio/reflexio/server/services/publish_learning_worker.py +288 -0
  105. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
  106. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +29 -13
  107. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +4 -0
  108. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
  109. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +43 -6
  110. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +10 -1167
  111. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +58 -351
  112. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +49 -19
  113. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +452 -0
  114. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -4
  115. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1 -2133
  116. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +7 -1126
  117. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +73 -33
  118. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +30 -0
  119. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/__init__.py +9 -0
  120. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py +506 -0
  121. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_pending_tool_call_store.py +704 -0
  122. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_run_tool_dependency_store.py +123 -0
  123. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/__init__.py +6 -0
  124. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +263 -0
  125. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +132 -0
  126. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/__init__.py +13 -0
  127. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_audit.py +122 -0
  128. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +465 -0
  129. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_purge.py +387 -0
  130. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py +332 -0
  131. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +511 -0
  132. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
  133. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +955 -0
  134. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
  135. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
  136. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
  137. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +844 -0
  138. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/__init__.py +9 -0
  139. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +263 -0
  140. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +896 -0
  141. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py +270 -0
  142. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +50 -9
  143. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +64 -370
  144. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +4 -4
  145. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +0 -909
  146. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +2 -0
  147. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +20 -0
  148. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/__init__.py +9 -0
  149. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py +86 -0
  150. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +195 -0
  151. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_pending_tool_call_store.py +148 -0
  152. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_run_tool_dependency_store.py +38 -0
  153. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/__init__.py +13 -0
  154. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_audit.py +29 -0
  155. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_erase_execution.py +30 -0
  156. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_purge.py +74 -0
  157. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py +32 -0
  158. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_subject_barrier.py +49 -0
  159. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
  160. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
  161. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
  162. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
  163. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
  164. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
  165. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/__init__.py +9 -0
  166. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +73 -0
  167. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/{_profiles.py → profiles/_profile_store.py} +57 -86
  168. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_search.py +32 -0
  169. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +153 -12
  170. package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +0 -84
@@ -127,6 +127,21 @@ class ReflexioBase:
127
127
  raise RuntimeError(STORAGE_NOT_CONFIGURED_MSG)
128
128
  return storage
129
129
 
130
+ def get_storage(self) -> BaseStorage:
131
+ """Return the configured storage backend.
132
+
133
+ Public accessor for consumers (including enterprise) that need the
134
+ underlying ``BaseStorage``. Prefer ``request_context.storage`` inside
135
+ OSS pipeline code; this exists for callers that hold a ``Reflexio``.
136
+
137
+ Returns:
138
+ BaseStorage: The configured storage backend.
139
+
140
+ Raises:
141
+ RuntimeError: If storage is not configured.
142
+ """
143
+ return self._get_storage()
144
+
130
145
  def _get_query_reformulator(self) -> QueryReformulator:
131
146
  """Lazily create and cache a QueryReformulator instance.
132
147
 
@@ -29,32 +29,37 @@ class ConfigMixin(ReflexioBase):
29
29
  # security). Without this, the marker reaches the readiness check as
30
30
  # an unfillable StorageConfigManagedSupabase and fails with
31
31
  # "Storage configuration is incomplete".
32
+ current_storage_config = configurator.get_current_storage_configuration()
32
33
  storage_config = config.storage_config
33
34
  if storage_config is None or isinstance(
34
35
  storage_config, StorageConfigManagedSupabase
35
36
  ):
36
- storage_config = configurator.get_current_storage_configuration()
37
+ storage_config = current_storage_config
37
38
  config.storage_config = storage_config
38
39
 
39
- # Check if storage config is ready to test
40
- if not configurator.is_storage_config_ready_to_test(
41
- storage_config=storage_config
42
- ):
43
- return SetConfigResponse(
44
- success=False, msg="Storage configuration is incomplete"
40
+ storage_config_changed = storage_config != current_storage_config
41
+ if storage_config_changed or current_storage_config is None:
42
+ # Storage validation initializes the backend and may run
43
+ # migrations, so only do it when the storage target changes.
44
+ if not configurator.is_storage_config_ready_to_test(
45
+ storage_config=storage_config
46
+ ):
47
+ return SetConfigResponse(
48
+ success=False, msg="Storage configuration is incomplete"
49
+ )
50
+
51
+ (
52
+ success,
53
+ error_msg,
54
+ ) = configurator.test_and_init_storage_config(
55
+ storage_config=storage_config
45
56
  )
46
57
 
47
- # Test and initialize storage connection
48
- (
49
- success,
50
- error_msg,
51
- ) = configurator.test_and_init_storage_config(storage_config=storage_config)
52
-
53
- if not success:
54
- return SetConfigResponse(
55
- success=False,
56
- msg=f"Failed to validate storage connection: {error_msg}",
57
- )
58
+ if not success:
59
+ return SetConfigResponse(
60
+ success=False,
61
+ msg=f"Failed to validate storage connection: {error_msg}",
62
+ )
58
63
 
59
64
  # Only set config if validation passed
60
65
  configurator.set_config(config)
@@ -38,22 +38,23 @@ class GenerationMixin(ReflexioBase):
38
38
  """
39
39
  if not self._is_storage_configured():
40
40
  raise ValueError(STORAGE_NOT_CONFIGURED_MSG)
41
+ from reflexio.server.extensions import get_service
42
+ from reflexio.server.services.playbook.aggregation_prompt_processing import (
43
+ AGGREGATION_PROMPT_PROCESSOR,
44
+ )
41
45
  from reflexio.server.services.playbook.components.aggregator import (
42
46
  PlaybookAggregator,
43
47
  )
44
48
  from reflexio.server.services.playbook.playbook_service_utils import (
45
49
  PlaybookAggregatorRequest,
46
50
  )
47
- from reflexio.server.services.playbook.user_detail_stripping import (
48
- create_aggregation_user_detail_stripper,
49
- )
50
51
 
51
- user_detail_stripper = create_aggregation_user_detail_stripper(
52
- self.request_context.configurator
53
- )
52
+ aggregation_prompt_processor = get_service(AGGREGATION_PROMPT_PROCESSOR)
54
53
  aggregator_kwargs = {}
55
- if user_detail_stripper is not None:
56
- aggregator_kwargs["user_detail_stripper"] = user_detail_stripper
54
+ if aggregation_prompt_processor is not None:
55
+ aggregator_kwargs["aggregation_prompt_processor"] = (
56
+ aggregation_prompt_processor
57
+ )
57
58
 
58
59
  playbook_aggregator = PlaybookAggregator(
59
60
  llm_client=self.llm_client,
@@ -53,11 +53,21 @@ class InteractionsMixin(ReflexioBase):
53
53
  def publish_interaction(
54
54
  self,
55
55
  request: PublishUserInteractionRequest | dict,
56
+ *,
57
+ use_publish_limiter: bool = True,
58
+ publish_limiter_wait_forever: bool = True,
59
+ defer_learning: bool = False,
56
60
  ) -> PublishUserInteractionResponse:
57
61
  """Publish user interactions.
58
62
 
59
63
  Args:
60
64
  request (Union[PublishUserInteractionRequest, dict]): The publish user interaction request
65
+ use_publish_limiter: Whether GenerationService should throttle the
66
+ post-write learning pipeline.
67
+ publish_limiter_wait_forever: Whether GenerationService should queue
68
+ indefinitely for that post-write learning limiter.
69
+ defer_learning: Whether to enqueue post-persist learning instead of
70
+ running it inline.
61
71
 
62
72
  Returns:
63
73
  PublishUserInteractionResponse: Response containing success status and message
@@ -87,7 +97,12 @@ class InteractionsMixin(ReflexioBase):
87
97
  # Convert dict to PublishUserInteractionRequest if needed
88
98
  if isinstance(request, dict):
89
99
  request = PublishUserInteractionRequest(**request)
90
- result = generation_service.run(request)
100
+ result = generation_service.run(
101
+ request,
102
+ use_publish_limiter=use_publish_limiter,
103
+ publish_limiter_wait_forever=publish_limiter_wait_forever,
104
+ defer_learning=defer_learning,
105
+ )
91
106
  after_profiles = _safe_count(storage.count_all_profiles)
92
107
  after_playbooks = _safe_count(storage.count_user_playbooks)
93
108
  # Don't concatenate warnings into the message field — they
@@ -414,26 +414,22 @@ class ProfilesMixin(ReflexioBase):
414
414
  status_filter = [None] # Default to current profiles
415
415
 
416
416
  profiles = self._get_storage().get_user_profile(
417
- request.user_id, status_filter=status_filter, tags=request.tags
417
+ request.user_id,
418
+ status_filter=status_filter,
419
+ tags=request.tags,
420
+ profile_id=request.profile_id,
421
+ query=request.query,
422
+ source=request.source,
423
+ profile_time_to_live=request.profile_time_to_live,
424
+ start_time=(
425
+ int(request.start_time.timestamp()) if request.start_time else None
426
+ ),
427
+ end_time=int(request.end_time.timestamp()) if request.end_time else None,
418
428
  )
419
429
  profiles = sorted(
420
430
  profiles, key=lambda x: x.last_modified_timestamp, reverse=True
421
431
  )
422
432
 
423
- # Apply time filters
424
- if request.start_time:
425
- profiles = [
426
- p
427
- for p in profiles
428
- if p.last_modified_timestamp >= int(request.start_time.timestamp())
429
- ]
430
- if request.end_time:
431
- profiles = [
432
- p
433
- for p in profiles
434
- if p.last_modified_timestamp <= int(request.end_time.timestamp())
435
- ]
436
-
437
433
  # Apply top_k limit
438
434
  if request.top_k:
439
435
  profiles = sorted(
@@ -450,6 +446,13 @@ class ProfilesMixin(ReflexioBase):
450
446
  self,
451
447
  limit: int = 100,
452
448
  status_filter: list[Status | None] | None = None,
449
+ user_id: str | None = None,
450
+ profile_id: str | None = None,
451
+ query: str | None = None,
452
+ source: str | None = None,
453
+ profile_time_to_live: str | None = None,
454
+ start_time: int | None = None,
455
+ end_time: int | None = None,
453
456
  ) -> GetUserProfilesResponse:
454
457
  """Get all user profiles across all users.
455
458
 
@@ -467,7 +470,15 @@ class ProfilesMixin(ReflexioBase):
467
470
  if status_filter is None:
468
471
  status_filter = [None] # Default to current profiles
469
472
  profiles = self._get_storage().get_all_profiles(
470
- limit=limit, status_filter=status_filter
473
+ limit=limit,
474
+ status_filter=status_filter,
475
+ user_id=user_id,
476
+ profile_id=profile_id,
477
+ query=query,
478
+ source=source,
479
+ profile_time_to_live=profile_time_to_live,
480
+ start_time=start_time,
481
+ end_time=end_time,
471
482
  )
472
483
  profiles = sorted(
473
484
  profiles, key=lambda x: x.last_modified_timestamp, reverse=True
@@ -41,9 +41,27 @@ class SearchMixin(ReflexioBase):
41
41
  request = GetAgentSuccessEvaluationResultsRequest(**request)
42
42
 
43
43
  try:
44
- results = self._get_storage().get_agent_success_evaluation_results(
45
- limit=request.limit or 100, agent_version=request.agent_version
46
- )
44
+ if request.start_time or request.end_time:
45
+ results = (
46
+ self._get_storage().get_agent_success_evaluation_results_in_window(
47
+ from_ts=(
48
+ int(request.start_time.timestamp())
49
+ if request.start_time
50
+ else 0
51
+ ),
52
+ to_ts=(
53
+ int(request.end_time.timestamp())
54
+ if request.end_time
55
+ else 2**31 - 1
56
+ ),
57
+ limit=request.limit or 100,
58
+ agent_version=request.agent_version,
59
+ )
60
+ )
61
+ else:
62
+ results = self._get_storage().get_agent_success_evaluation_results(
63
+ limit=request.limit or 100, agent_version=request.agent_version
64
+ )
47
65
  return GetAgentSuccessEvaluationResultsResponse(
48
66
  success=True,
49
67
  agent_success_evaluation_results=results,
@@ -79,6 +97,7 @@ class SearchMixin(ReflexioBase):
79
97
  user_id=request.user_id,
80
98
  request_id=request.request_id,
81
99
  session_id=request.session_id,
100
+ source=request.source,
82
101
  start_time=(
83
102
  int(request.start_time.timestamp()) if request.start_time else None
84
103
  ),
@@ -104,10 +123,11 @@ class SearchMixin(ReflexioBase):
104
123
  Session(session_id=group_name, requests=request_data_list)
105
124
  )
106
125
 
107
- # Determine has_more: count total requests returned across all groups
126
+ # Pagination is session-based (top_k counts sessions): there may be
127
+ # more pages when this page filled the session limit.
108
128
  total_returned = sum(len(s.requests) for s in sessions)
109
129
  effective_limit = request.top_k or 100
110
- has_more = total_returned >= effective_limit
130
+ has_more = len(sessions) >= effective_limit
111
131
 
112
132
  return GetRequestsResponse(
113
133
  success=True,
@@ -162,6 +182,7 @@ class SearchMixin(ReflexioBase):
162
182
  storage = self._get_storage()
163
183
  prompt_manager = self.request_context.prompt_manager
164
184
  retrieval_floor = config.retrieval_floor if config else None
185
+ recency = getattr(storage, "recency", None)
165
186
 
166
187
  from reflexio.server.services.unified_search_service import run_unified_search
167
188
 
@@ -173,4 +194,5 @@ class SearchMixin(ReflexioBase):
173
194
  prompt_manager=prompt_manager,
174
195
  pre_retrieval_model_name=pre_retrieval_model_name,
175
196
  retrieval_floor=retrieval_floor,
197
+ recency=recency,
176
198
  )
@@ -53,10 +53,19 @@ class UserPlaybookMixin(ReflexioBase):
53
53
  try:
54
54
  user_playbooks = self._get_storage().get_user_playbooks(
55
55
  limit=request.limit or 100,
56
+ user_playbook_id=request.user_playbook_id,
56
57
  user_id=request.user_id,
58
+ request_id=request.request_id,
59
+ query=request.query,
57
60
  playbook_name=request.playbook_name,
58
61
  agent_version=request.agent_version,
59
62
  status_filter=request.status_filter,
63
+ start_time=(
64
+ int(request.start_time.timestamp()) if request.start_time else None
65
+ ),
66
+ end_time=(
67
+ int(request.end_time.timestamp()) if request.end_time else None
68
+ ),
60
69
  tags=request.tags,
61
70
  )
62
71
  return GetUserPlaybooksResponse(
@@ -1,3 +1,4 @@
1
1
  from ..common import * # noqa: F401, F403
2
2
  from .entities import * # noqa: F401, F403
3
3
  from .enums import * # noqa: F401, F403
4
+ from .governance import * # noqa: F401, F403
@@ -46,6 +46,7 @@ class Status(str, Enum): # noqa: UP042 - CURRENT=None is not compatible with St
46
46
  SUPERSEDED = (
47
47
  "superseded" # tombstone: replaced by a new version (superseded_by set)
48
48
  )
49
+ EXPIRED = "expired" # tombstone: TTL elapsed while active (see expiry reclamation)
49
50
 
50
51
 
51
52
  class OperationStatus(StrEnum):
@@ -0,0 +1,117 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import UTC, datetime
4
+ from typing import Any, Literal
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ AuditActorType = Literal["api_token", "jwt", "system"]
9
+ AuditOperation = Literal["READ", "EXPORT", "ERASE", "CREATE", "UPDATE", "DELETE"]
10
+ AuditEntityType = Literal[
11
+ "profile",
12
+ "user_playbook",
13
+ "agent_playbook",
14
+ "interaction",
15
+ "request",
16
+ "session",
17
+ "agent_success_evaluation_result",
18
+ "playbook_retrieval_log",
19
+ "org",
20
+ ]
21
+ AuditStatus = Literal["ok", "error"]
22
+ PurgeOperationType = Literal["user_erasure", "org_purge"]
23
+ PurgeScopeType = Literal["user", "org"]
24
+ PurgeStatus = Literal["pending", "running", "failed", "complete"]
25
+ PurgeTargetStatus = Literal["pending", "running", "failed", "complete"]
26
+ SubjectBarrierStatus = Literal["erasing", "erased", "failed"]
27
+
28
+ __all__ = [
29
+ "AuditActorType",
30
+ "AuditOperation",
31
+ "AuditEntityType",
32
+ "AuditStatus",
33
+ "PurgeOperationType",
34
+ "PurgeScopeType",
35
+ "PurgeStatus",
36
+ "PurgeTargetStatus",
37
+ "SubjectBarrierStatus",
38
+ "AuditEvent",
39
+ "PurgeOperation",
40
+ "PurgeOperationTarget",
41
+ "SubjectWriteBarrier",
42
+ "UserExportResult",
43
+ "UserEraseResult",
44
+ ]
45
+
46
+
47
+ def _now_epoch() -> int:
48
+ return int(datetime.now(UTC).timestamp())
49
+
50
+
51
+ class AuditEvent(BaseModel):
52
+ org_id: str
53
+ actor_type: AuditActorType = "system"
54
+ actor_ref: str | None = None
55
+ operation: AuditOperation
56
+ entity_type: AuditEntityType
57
+ entity_id: str | None = None
58
+ subject_ref: str | None = None
59
+ request_ref: str
60
+ idempotency_key: str | None = None
61
+ status: AuditStatus = "ok"
62
+ detail: dict[str, Any] | None = None
63
+ created_at: int = Field(default_factory=_now_epoch)
64
+
65
+
66
+ class PurgeOperation(BaseModel):
67
+ purge_id: str
68
+ org_id: str
69
+ operation_type: PurgeOperationType
70
+ scope_type: PurgeScopeType
71
+ subject_ref: str | None = None
72
+ request_ref: str
73
+ idempotency_key: str
74
+ status: PurgeStatus = "pending"
75
+ error_code: str | None = None
76
+ error_detail: str | None = None
77
+ created_at: int = Field(default_factory=_now_epoch)
78
+ updated_at: int = Field(default_factory=_now_epoch)
79
+ completed_at: int | None = None
80
+
81
+
82
+ class PurgeOperationTarget(BaseModel):
83
+ purge_id: str
84
+ target_name: str
85
+ target_ref: str = ""
86
+ phase: str
87
+ status: PurgeTargetStatus = "pending"
88
+ detail: dict[str, Any] | None = None
89
+ deleted_count: int = 0
90
+ error_detail: str | None = None
91
+ started_at: int | None = None
92
+ completed_at: int | None = None
93
+
94
+
95
+ class SubjectWriteBarrier(BaseModel):
96
+ org_id: str
97
+ subject_ref: str
98
+ purge_id: str
99
+ status: SubjectBarrierStatus
100
+ error_code: str | None = None
101
+ error_detail: str | None = None
102
+ created_at: int = Field(default_factory=_now_epoch)
103
+ updated_at: int = Field(default_factory=_now_epoch)
104
+
105
+
106
+ class UserExportResult(BaseModel):
107
+ subject_ref: str
108
+ export_id: str
109
+ bundle: dict[str, Any]
110
+
111
+
112
+ class UserEraseResult(BaseModel):
113
+ subject_ref: str
114
+ purge_id: str
115
+ status: PurgeStatus
116
+ deleted_counts: dict[str, int] = Field(default_factory=dict)
117
+ rebuilt_agent_playbook_ids: list[int] = Field(default_factory=list)
@@ -187,9 +187,13 @@ class GetInteractionsResponse(BaseModel):
187
187
 
188
188
  class GetUserProfilesRequest(BaseModel):
189
189
  user_id: NonEmptyStr
190
+ profile_id: str | None = None
191
+ query: str | None = None
190
192
  start_time: datetime | None = None
191
193
  end_time: datetime | None = None
192
194
  top_k: int | None = Field(default=30, gt=0)
195
+ source: str | None = None
196
+ profile_time_to_live: str | None = None
193
197
  status_filter: list[Status | None] | None = None
194
198
  tags: list[str] | None = None
195
199
 
@@ -222,12 +226,23 @@ class SetConfigResponse(BaseModel):
222
226
 
223
227
  class GetUserPlaybooksRequest(BaseModel):
224
228
  limit: int | None = Field(default=100, gt=0)
229
+ user_playbook_id: int | None = Field(default=None, gt=0)
225
230
  user_id: str | None = None
231
+ request_id: str | None = None
232
+ query: str | None = None
226
233
  playbook_name: str | None = None
227
234
  agent_version: str | None = None
235
+ start_time: datetime | None = None
236
+ end_time: datetime | None = None
228
237
  status_filter: list[Status | None] | None = None
229
238
  tags: list[str] | None = None
230
239
 
240
+ @model_validator(mode="after")
241
+ def check_time_range(self) -> Self:
242
+ """Validate that end_time is after start_time."""
243
+ TimeRangeValidatorMixin.validate_time_range(self.start_time, self.end_time)
244
+ return self
245
+
231
246
 
232
247
  class GetUserPlaybooksResponse(BaseModel):
233
248
  success: bool
@@ -237,8 +252,12 @@ class GetUserPlaybooksResponse(BaseModel):
237
252
 
238
253
  class GetAgentPlaybooksRequest(BaseModel):
239
254
  limit: int | None = Field(default=100, gt=0)
255
+ agent_playbook_id: int | None = Field(default=None, gt=0)
256
+ query: str | None = None
240
257
  playbook_name: str | None = None
241
258
  agent_version: str | None = None
259
+ start_time: datetime | None = None
260
+ end_time: datetime | None = None
242
261
  status_filter: list[Status | None] | None = None
243
262
  playbook_status_filter: PlaybookStatus | None = None
244
263
  tags: list[str] | None = None
@@ -247,6 +266,12 @@ class GetAgentPlaybooksRequest(BaseModel):
247
266
  request_id: str | None = None
248
267
  session_id: str | None = None
249
268
 
269
+ @model_validator(mode="after")
270
+ def check_time_range(self) -> Self:
271
+ """Validate that end_time is after start_time."""
272
+ TimeRangeValidatorMixin.validate_time_range(self.start_time, self.end_time)
273
+ return self
274
+
250
275
 
251
276
  class GetAgentPlaybooksResponse(BaseModel):
252
277
  success: bool
@@ -392,6 +417,14 @@ class SearchAgentPlaybookResponse(BaseModel):
392
417
  class GetAgentSuccessEvaluationResultsRequest(BaseModel):
393
418
  limit: int | None = Field(default=100, gt=0)
394
419
  agent_version: str | None = None
420
+ start_time: datetime | None = None
421
+ end_time: datetime | None = None
422
+
423
+ @model_validator(mode="after")
424
+ def check_time_range(self) -> Self:
425
+ """Validate that end_time is after start_time."""
426
+ TimeRangeValidatorMixin.validate_time_range(self.start_time, self.end_time)
427
+ return self
395
428
 
396
429
 
397
430
  class GetAgentSuccessEvaluationResultsResponse(BaseModel):
@@ -404,10 +437,18 @@ class GetRequestsRequest(BaseModel):
404
437
  user_id: str | None = None
405
438
  request_id: str | None = None
406
439
  session_id: str | None = None
440
+ source: str | None = None
407
441
  start_time: datetime | None = None
408
442
  end_time: datetime | None = None
409
- top_k: int | None = Field(default=30, gt=0)
410
- offset: int | None = Field(default=0, ge=0)
443
+ top_k: int | None = Field(
444
+ default=30,
445
+ gt=0,
446
+ description="Maximum number of sessions to return. Pagination is "
447
+ "per-session: every returned session includes all of its requests.",
448
+ )
449
+ offset: int | None = Field(
450
+ default=0, ge=0, description="Number of sessions to skip for pagination."
451
+ )
411
452
 
412
453
  @model_validator(mode="after")
413
454
  def check_time_range(self) -> Self:
@@ -497,7 +538,8 @@ class TimeSeriesDataPoint(BaseModel):
497
538
  """A single data point in a time series."""
498
539
 
499
540
  timestamp: int = Field(gt=0) # Unix timestamp
500
- value: int = Field(ge=0) # Count or metric value
541
+ value: float = Field(ge=0) # Count or metric value
542
+ count: int | None = Field(default=None, ge=0) # Optional weight for rate metrics
501
543
 
502
544
 
503
545
  class PeriodStats(BaseModel):
@@ -708,6 +708,34 @@ class LineageGCConfig(BaseModel):
708
708
  poll_interval_seconds: int = Field(default=86400, gt=0)
709
709
 
710
710
 
711
+ class ExpiryReclamationConfig(BaseModel):
712
+ """Direct-delete reclamation of expired plain rows (non-audited).
713
+
714
+ Independent of ``lineage_gc``: these rows carry no PII/audit/grace obligation,
715
+ so they can be reclaimed whenever this is enabled even if tombstone GC is off.
716
+
717
+ Opt-in by default (``enabled=False``) so operators control a staged rollout
718
+ of the direct-delete Class B sweeps and are not surprised by deletions on
719
+ upgrade. ``lineage_gc.enabled`` (which defaults to True) is unaffected and
720
+ continues to drive the Class A profile-expiry and tombstone-GC paths.
721
+ """
722
+
723
+ enabled: bool = False
724
+
725
+
726
+ class GovernanceRetentionConfig(BaseModel):
727
+ """Audit-event retention policy. **Enterprise-only:** reclamation is performed
728
+ by an enterprise per-org reclamation sweep registered via
729
+ ``register_per_org_sweep``. In an OSS-only deployment these knobs are accepted
730
+ but inert (the OSS lineage scheduler does not reclaim audit events) and the
731
+ server logs a startup warning when retention is enabled.
732
+ """
733
+
734
+ audit_events_retention_enabled: bool = False
735
+ audit_events_retention_days: int = Field(default=365, gt=0)
736
+ audit_events_delete_batch_limit: int = Field(default=500, gt=0)
737
+
738
+
711
739
  @dataclass(frozen=True)
712
740
  class EffectivePendingToolCallConfig:
713
741
  """Resolved pending-tool-call settings after applying tool overrides."""
@@ -847,6 +875,13 @@ class Config(BaseModel):
847
875
  )
848
876
  # Tombstone GC job gate (opt-in, off by default — see LineageGCConfig)
849
877
  lineage_gc: LineageGCConfig = Field(default_factory=LineageGCConfig)
878
+ # Direct-delete reclamation of expired plain rows (share links, pending tool calls)
879
+ expiry_reclamation: ExpiryReclamationConfig = Field(
880
+ default_factory=ExpiryReclamationConfig
881
+ )
882
+ governance_retention: GovernanceRetentionConfig = Field(
883
+ default_factory=GovernanceRetentionConfig
884
+ )
850
885
  # Optional non-blocking async information tools for classic extraction.
851
886
  pending_tool_call_config: PendingToolCallConfig = Field(
852
887
  default_factory=PendingToolCallConfig
@@ -905,6 +940,8 @@ class Config(BaseModel):
905
940
  "reflection_config",
906
941
  "playbook_optimizer_config",
907
942
  "lineage_gc",
943
+ "expiry_reclamation",
944
+ "governance_retention",
908
945
  "pending_tool_call_config",
909
946
  "retrieval_floor",
910
947
  ):