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
@@ -0,0 +1,47 @@
1
+ """Abstract agent playbook source-linkage declarations."""
2
+
3
+ from abc import abstractmethod
4
+ from collections.abc import Sequence
5
+
6
+ from reflexio.models.api_schema.domain import AgentPlaybookSourceWindow
7
+
8
+
9
+ class PlaybookSourceLinkageMixin:
10
+ """Abstract agent playbook source-linkage methods."""
11
+
12
+ @abstractmethod
13
+ def set_source_user_playbook_ids_for_agent_playbook(
14
+ self, agent_playbook_id: int, user_playbook_ids: list[int]
15
+ ) -> None:
16
+ """Persist the source user playbook ids that produced an agent playbook."""
17
+ raise NotImplementedError
18
+
19
+ @abstractmethod
20
+ def get_source_user_playbook_ids_for_agent_playbook(
21
+ self, agent_playbook_id: int
22
+ ) -> list[int]:
23
+ """Return source user playbook ids for an agent playbook."""
24
+ raise NotImplementedError
25
+
26
+ @abstractmethod
27
+ def get_source_user_playbook_ids_for_agent_playbooks(
28
+ self, agent_playbook_ids: Sequence[int]
29
+ ) -> dict[int, list[int]]:
30
+ """Return source user playbook ids keyed by agent playbook id."""
31
+ raise NotImplementedError
32
+
33
+ @abstractmethod
34
+ def set_source_windows_for_agent_playbook(
35
+ self,
36
+ agent_playbook_id: int,
37
+ source_windows: list[AgentPlaybookSourceWindow],
38
+ ) -> None:
39
+ """Persist replayable source windows that produced an agent playbook."""
40
+ raise NotImplementedError
41
+
42
+ @abstractmethod
43
+ def get_source_windows_for_agent_playbook(
44
+ self, agent_playbook_id: int
45
+ ) -> list[AgentPlaybookSourceWindow]:
46
+ """Return replayable source windows for an agent playbook."""
47
+ raise NotImplementedError
@@ -0,0 +1,333 @@
1
+ """Abstract user playbook CRUD + search declarations."""
2
+
3
+ from abc import abstractmethod
4
+
5
+ from reflexio.models.api_schema.common import BlockingIssue
6
+ from reflexio.models.api_schema.domain import Status, UserPlaybook
7
+ from reflexio.models.api_schema.retriever_schema import SearchUserPlaybookRequest
8
+ from reflexio.models.config_schema import SearchOptions
9
+
10
+
11
+ class UserPlaybookStoreMixin:
12
+ """Abstract user playbook CRUD + search methods."""
13
+
14
+ @abstractmethod
15
+ def save_user_playbooks(self, user_playbooks: list[UserPlaybook]) -> None:
16
+ raise NotImplementedError
17
+
18
+ @abstractmethod
19
+ def get_user_playbooks(
20
+ self,
21
+ limit: int = 100,
22
+ user_id: str | None = None,
23
+ playbook_name: str | None = None,
24
+ agent_version: str | None = None,
25
+ status_filter: list[Status | None] | None = None,
26
+ start_time: int | None = None,
27
+ end_time: int | None = None,
28
+ include_embedding: bool = False,
29
+ tags: list[str] | None = None,
30
+ offset: int = 0,
31
+ user_playbook_id: int | None = None,
32
+ request_id: str | None = None,
33
+ query: str | None = None,
34
+ ) -> list[UserPlaybook]:
35
+ """Get user playbooks from storage.
36
+
37
+ Args:
38
+ limit (int): Maximum number of playbooks to return
39
+ user_playbook_id (int, optional): Exact user playbook ID to retrieve.
40
+ user_id (str, optional): The user ID to filter by. If None, returns playbooks for all users.
41
+ request_id (str, optional): Request ID that generated the playbook.
42
+ query (str, optional): Case-insensitive text filter across visible fields.
43
+ playbook_name (str, optional): The playbook name to filter by. If None, returns all user playbooks.
44
+ agent_version (str, optional): The agent version to filter by. If None, returns all agent versions.
45
+ status_filter (list[Optional[Status]], optional): List of status values to filter by.
46
+ Can include None (current), Status.PENDING (from rerun), Status.ARCHIVED (old).
47
+ If None, returns playbooks with all statuses.
48
+ start_time (int, optional): Unix timestamp. Only return playbooks created at or after this time.
49
+ end_time (int, optional): Unix timestamp. Only return playbooks created at or before this time.
50
+ include_embedding (bool): If True, fetch and parse embedding vectors. Defaults to False.
51
+ tags (list[str], optional): Match playbooks having any of these tags.
52
+ offset (int): Number of matching rows to skip. Defaults to 0.
53
+
54
+ Returns:
55
+ list[UserPlaybook]: List of user playbook objects
56
+ """
57
+ raise NotImplementedError
58
+
59
+ @abstractmethod
60
+ def count_user_playbooks(
61
+ self,
62
+ user_id: str | None = None,
63
+ playbook_name: str | None = None,
64
+ min_user_playbook_id: int | None = None,
65
+ agent_version: str | None = None,
66
+ status_filter: list[Status | None] | None = None,
67
+ ) -> int:
68
+ """Count user playbooks in storage efficiently.
69
+
70
+ Args:
71
+ user_id (str, optional): The user ID to filter by. If None, counts playbooks for all users.
72
+ playbook_name (str, optional): The playbook name to filter by. If None, counts all user playbooks.
73
+ min_user_playbook_id (int, optional): Only count playbooks with user_playbook_id greater than this value.
74
+ agent_version (str, optional): The agent version to filter by. If None, counts all agent versions.
75
+ status_filter (list[Optional[Status]], optional): List of status values to filter by.
76
+ Can include None (current), Status.PENDING (from rerun), Status.ARCHIVED (old).
77
+ If None, returns playbooks with all statuses.
78
+
79
+ Returns:
80
+ int: Count of user playbooks matching the filters
81
+ """
82
+ raise NotImplementedError
83
+
84
+ @abstractmethod
85
+ def count_user_playbooks_by_session(self, session_id: str) -> int:
86
+ """Count user playbooks linked to a session via request_id -> requests.session_id.
87
+
88
+ Args:
89
+ session_id (str): The session ID to count user playbooks for
90
+
91
+ Returns:
92
+ int: Count of user playbooks linked to the session
93
+ """
94
+ raise NotImplementedError
95
+
96
+ @abstractmethod
97
+ def delete_all_user_playbooks(self) -> None:
98
+ """Delete all user playbooks from storage."""
99
+ raise NotImplementedError
100
+
101
+ @abstractmethod
102
+ def delete_all_user_playbooks_by_playbook_name(
103
+ self, playbook_name: str, agent_version: str | None = None
104
+ ) -> None:
105
+ """Delete all user playbooks by playbook name from storage.
106
+
107
+ Args:
108
+ playbook_name (str): The playbook name to delete
109
+ agent_version (str, optional): The agent version to filter by. If None, deletes all agent versions.
110
+ """
111
+ raise NotImplementedError
112
+
113
+ @abstractmethod
114
+ def delete_user_playbook(self, user_playbook_id: int) -> None:
115
+ """Delete a user playbook by ID.
116
+
117
+ Args:
118
+ user_playbook_id (int): The ID of the user playbook to delete
119
+ """
120
+ raise NotImplementedError
121
+
122
+ @abstractmethod
123
+ def update_all_user_playbooks_status(
124
+ self,
125
+ old_status: Status | None,
126
+ new_status: Status | None,
127
+ agent_version: str | None = None,
128
+ playbook_name: str | None = None,
129
+ ) -> int:
130
+ """Update all user playbooks with old_status to new_status atomically.
131
+
132
+ Args:
133
+ old_status: The current status to match (None for CURRENT)
134
+ new_status: The new status to set (None for CURRENT)
135
+ agent_version: Optional filter by agent version
136
+ playbook_name: Optional filter by playbook name
137
+
138
+ Returns:
139
+ int: Number of user playbooks updated
140
+ """
141
+ raise NotImplementedError
142
+
143
+ @abstractmethod
144
+ def delete_all_user_playbooks_by_status(
145
+ self,
146
+ status: Status,
147
+ agent_version: str | None = None,
148
+ playbook_name: str | None = None,
149
+ ) -> int:
150
+ """Delete all user playbooks with the given status atomically.
151
+
152
+ Args:
153
+ status: The status of user playbooks to delete
154
+ agent_version: Optional filter by agent version
155
+ playbook_name: Optional filter by playbook name
156
+
157
+ Returns:
158
+ int: Number of user playbooks deleted
159
+ """
160
+ raise NotImplementedError
161
+
162
+ @abstractmethod
163
+ def delete_user_playbooks_by_ids(
164
+ self, user_playbook_ids: list[int], *, emit_hard_delete: bool = True
165
+ ) -> int:
166
+ """Delete user playbooks by their IDs.
167
+
168
+ Args:
169
+ user_playbook_ids: List of user_playbook_id values to delete
170
+ emit_hard_delete: When True (default), append a ``hard_delete``
171
+ lineage event per id (genuine erasure). Set False for rollback
172
+ cleanup of a never-live row (e.g. a lost supersede CAS), so no
173
+ spurious audit event is recorded.
174
+
175
+ Returns:
176
+ int: Number of user playbooks deleted
177
+ """
178
+ raise NotImplementedError
179
+
180
+ @abstractmethod
181
+ def get_user_playbooks_by_ids(
182
+ self,
183
+ user_id: str,
184
+ user_playbook_ids: list[int],
185
+ status_filter: list[Status | None] | None = None,
186
+ ) -> list[UserPlaybook]:
187
+ """Fetch the subset of a user's playbooks whose ids are in the list.
188
+
189
+ Server-side filter on (``user_id``, ``user_playbook_id IN (...)``)
190
+ so callers (e.g. the reflection service resolving a small set of
191
+ cited playbook ids) avoid scanning every playbook for the user.
192
+
193
+ Args:
194
+ user_id (str): Owning user id.
195
+ user_playbook_ids (list[int]): Playbook ids to fetch. Empty
196
+ list returns ``[]`` without hitting storage.
197
+ status_filter (list[Status | None] | None): Statuses to
198
+ include. ``None`` (default) means CURRENT only — same
199
+ default as ``get_user_playbooks`` for consistency.
200
+
201
+ Returns:
202
+ list[UserPlaybook]: Matching playbooks. Order is unspecified.
203
+ Ids that do not exist (or do not match the user / status
204
+ filter) are silently omitted.
205
+ """
206
+ raise NotImplementedError
207
+
208
+ @abstractmethod
209
+ def get_user_playbook_by_id(
210
+ self, user_playbook_id: int, *, include_tombstones: bool = False
211
+ ) -> UserPlaybook | None:
212
+ """Fetch one user playbook by primary key.
213
+
214
+ Args:
215
+ user_playbook_id: The user_playbook_id to look up.
216
+ include_tombstones: When False (default), MERGED/SUPERSEDED rows
217
+ return None. Set to True for lineage resolution (resolve_current).
218
+
219
+ Returns:
220
+ The UserPlaybook if found and not filtered, otherwise None.
221
+ """
222
+ raise NotImplementedError
223
+
224
+ @abstractmethod
225
+ def get_user_playbooks_by_ids_any_user(
226
+ self,
227
+ user_playbook_ids: list[int],
228
+ status_filter: list[Status | None] | None = None,
229
+ ) -> list[UserPlaybook]:
230
+ """Fetch user playbooks by ids without requiring a single owner id."""
231
+ raise NotImplementedError
232
+
233
+ @abstractmethod
234
+ def archive_user_playbook_by_id(self, user_id: str, user_playbook_id: int) -> bool:
235
+ """Atomically archive a single user playbook by id, only if CURRENT.
236
+
237
+ Flips the row's ``status`` from ``None`` (CURRENT) to
238
+ ``Status.ARCHIVED``. No-op when the playbook does not exist, has
239
+ a different ``user_id``, or is already non-current.
240
+
241
+ Args:
242
+ user_id (str): Owning user id; used as a guard so callers
243
+ cannot accidentally archive another user's playbook.
244
+ user_playbook_id (int): The user_playbook_id to archive.
245
+
246
+ Returns:
247
+ bool: True if a row was archived; False otherwise.
248
+ """
249
+ raise NotImplementedError
250
+
251
+ @abstractmethod
252
+ def has_user_playbooks_with_status(
253
+ self,
254
+ status: Status | None,
255
+ agent_version: str | None = None,
256
+ playbook_name: str | None = None,
257
+ ) -> bool:
258
+ """Check if any user playbooks exist with given status and filters.
259
+
260
+ Args:
261
+ status: The status to check for (None for CURRENT)
262
+ agent_version: Optional filter by agent version
263
+ playbook_name: Optional filter by playbook name
264
+
265
+ Returns:
266
+ bool: True if any matching user playbooks exist
267
+ """
268
+ raise NotImplementedError
269
+
270
+ @abstractmethod
271
+ def update_user_playbook(
272
+ self,
273
+ user_playbook_id: int,
274
+ playbook_name: str | None = None,
275
+ content: str | None = None,
276
+ trigger: str | None = None,
277
+ rationale: str | None = None,
278
+ blocking_issue: BlockingIssue | None = None,
279
+ tags: list[str] | None = None,
280
+ ) -> None:
281
+ """Update editable fields of a user playbook. Only non-None fields are updated.
282
+
283
+ Args:
284
+ user_playbook_id (int): The ID of the user playbook to update
285
+ playbook_name (str, optional): New playbook name
286
+ content (str, optional): New content text
287
+ trigger (str, optional): New trigger text
288
+ rationale (str, optional): New rationale text
289
+ blocking_issue (BlockingIssue, optional): New blocking issue
290
+ tags (list[str], optional): Replacement tags
291
+
292
+ Raises:
293
+ ValueError: If user playbook with the given ID is not found
294
+ """
295
+ raise NotImplementedError
296
+
297
+ @abstractmethod
298
+ def supersede_user_playbooks_by_ids(
299
+ self, user_playbook_ids: list[int], request_id: str
300
+ ) -> int:
301
+ """Soft-delete user playbooks by setting status to SUPERSEDED.
302
+
303
+ Eligible rows (CURRENT, PENDING, or ARCHIVED; not already MERGED /
304
+ SUPERSEDED) are transitioned to SUPERSEDED and emit one status_change
305
+ lineage event under the shared request id. This is the user-playbook
306
+ analogue of the existing agent/profile soft-supersede helpers and
307
+ preserves dead-source content for point-in-time attribution reads.
308
+
309
+ Args:
310
+ user_playbook_ids (list[int]): User playbook ids to supersede.
311
+ request_id (str): Shared request id for all emitted lineage events.
312
+
313
+ Returns:
314
+ int: Number of user playbooks actually updated.
315
+ """
316
+ raise NotImplementedError
317
+
318
+ @abstractmethod
319
+ def search_user_playbooks(
320
+ self,
321
+ request: SearchUserPlaybookRequest,
322
+ options: SearchOptions | None = None,
323
+ ) -> list[UserPlaybook]:
324
+ """Search user playbooks with advanced filtering including semantic search.
325
+
326
+ Args:
327
+ request (SearchUserPlaybookRequest): Search request with query, filters, and pagination
328
+ options (SearchOptions, optional): Engine-level search parameters (e.g. pre-computed embedding)
329
+
330
+ Returns:
331
+ list[UserPlaybook]: List of matching user playbook objects
332
+ """
333
+ raise NotImplementedError
@@ -0,0 +1,9 @@
1
+ from ._interaction_store import InteractionStoreMixin
2
+ from ._profile_store import ProfileStoreMixin
3
+ from ._search import ProfileSearchMixin
4
+
5
+ __all__ = [
6
+ "InteractionStoreMixin",
7
+ "ProfileSearchMixin",
8
+ "ProfileStoreMixin",
9
+ ]
@@ -0,0 +1,73 @@
1
+ from abc import abstractmethod
2
+
3
+ from reflexio.models.api_schema.domain import (
4
+ DeleteUserInteractionRequest,
5
+ Interaction,
6
+ )
7
+
8
+
9
+ class InteractionStoreMixin:
10
+ """Mixin for interaction-store CRUD methods."""
11
+
12
+ @abstractmethod
13
+ def get_all_interactions(self, limit: int = 100) -> list[Interaction]:
14
+ raise NotImplementedError
15
+
16
+ @abstractmethod
17
+ def get_user_interaction(self, user_id: str) -> list[Interaction]:
18
+ raise NotImplementedError
19
+
20
+ @abstractmethod
21
+ def get_all_user_ids(self) -> list[str]:
22
+ """Return distinct user IDs that have stored interactions."""
23
+ raise NotImplementedError
24
+
25
+ @abstractmethod
26
+ def add_user_interaction(self, user_id: str, interaction: Interaction) -> None:
27
+ raise NotImplementedError
28
+
29
+ @abstractmethod
30
+ def add_user_interactions_bulk(
31
+ self, user_id: str, interactions: list[Interaction]
32
+ ) -> None:
33
+ """Add multiple user interactions with batched embedding generation.
34
+
35
+ Args:
36
+ user_id: The user ID
37
+ interactions: List of interactions to add
38
+ """
39
+ raise NotImplementedError
40
+
41
+ @abstractmethod
42
+ def delete_user_interaction(self, request: DeleteUserInteractionRequest) -> None:
43
+ raise NotImplementedError
44
+
45
+ @abstractmethod
46
+ def delete_all_interactions_for_user(self, user_id: str) -> None:
47
+ raise NotImplementedError
48
+
49
+ @abstractmethod
50
+ def delete_all_interactions(self) -> None:
51
+ """Delete all interactions across all users."""
52
+ raise NotImplementedError
53
+
54
+ @abstractmethod
55
+ def count_all_interactions(self) -> int:
56
+ """Count total interactions across all users.
57
+
58
+ Returns:
59
+ int: Total number of interactions
60
+ """
61
+ raise NotImplementedError
62
+
63
+ @abstractmethod
64
+ def delete_oldest_interactions(self, count: int) -> int:
65
+ """Delete the oldest N interactions based on created_at timestamp.
66
+
67
+ Args:
68
+ count (int): Number of oldest interactions to delete
69
+
70
+ Returns:
71
+ int: Number of interactions actually deleted
72
+ """
73
+ raise NotImplementedError
@@ -1,72 +1,73 @@
1
1
  from abc import abstractmethod
2
2
 
3
3
  from reflexio.models.api_schema.domain import (
4
- DeleteUserInteractionRequest,
5
4
  DeleteUserProfileRequest,
6
- Interaction,
7
5
  Status,
8
6
  UserProfile,
9
7
  )
10
- from reflexio.models.api_schema.retriever_schema import (
11
- SearchInteractionRequest,
12
- SearchUserProfileRequest,
13
- )
14
8
 
15
9
 
16
- class ProfileMixin:
17
- """Mixin for profile and interaction CRUD methods."""
10
+ class ProfileStoreMixin:
11
+ """Mixin for profile-store CRUD methods."""
18
12
 
19
- # read methods
20
13
  @abstractmethod
21
14
  def get_all_profiles(
22
15
  self,
23
16
  limit: int = 100,
24
17
  status_filter: list[Status | None] | None = None,
18
+ user_id: str | None = None,
19
+ profile_id: str | None = None,
20
+ query: str | None = None,
21
+ source: str | None = None,
22
+ profile_time_to_live: str | None = None,
23
+ start_time: int | None = None,
24
+ end_time: int | None = None,
25
25
  ) -> list[UserProfile]:
26
26
  raise NotImplementedError
27
27
 
28
- @abstractmethod
29
- def get_all_interactions(self, limit: int = 100) -> list[Interaction]:
30
- raise NotImplementedError
31
-
32
28
  @abstractmethod
33
29
  def get_user_profile(
34
30
  self,
35
31
  user_id: str,
36
32
  status_filter: list[Status | None] | None = None,
37
33
  tags: list[str] | None = None,
34
+ profile_id: str | None = None,
35
+ query: str | None = None,
36
+ source: str | None = None,
37
+ profile_time_to_live: str | None = None,
38
+ start_time: int | None = None,
39
+ end_time: int | None = None,
40
+ include_expired: bool = False,
38
41
  ) -> list[UserProfile]:
39
- raise NotImplementedError
40
-
41
- @abstractmethod
42
- def get_user_interaction(self, user_id: str) -> list[Interaction]:
43
- raise NotImplementedError
44
-
45
- # create or update methods
46
- @abstractmethod
47
- def add_user_profile(self, user_id: str, user_profiles: list[UserProfile]) -> None:
48
- """Add the user profile for a given user id."""
49
- raise NotImplementedError
50
-
51
- @abstractmethod
52
- def add_user_interaction(self, user_id: str, interaction: Interaction) -> None:
53
- raise NotImplementedError
54
-
55
- @abstractmethod
56
- def add_user_interactions_bulk(
57
- self, user_id: str, interactions: list[Interaction]
58
- ) -> None:
59
- """Add multiple user interactions with batched embedding generation.
42
+ """Return profiles for a user, optionally including expired rows.
60
43
 
61
44
  Args:
62
- user_id: The user ID
63
- interactions: List of interactions to add
45
+ user_id: The user whose profiles to return.
46
+ status_filter: Statuses to include (``None`` element = CURRENT).
47
+ Defaults to ``[None]`` (CURRENT only) when not supplied.
48
+ tags: If provided, only return profiles whose ``tags`` overlap.
49
+ profile_id: If provided, filter to this exact profile id.
50
+ query: Free-text substring match across content, profile_id, user_id.
51
+ source: If provided, filter to this exact source value.
52
+ profile_time_to_live: If provided, filter to this TTL value.
53
+ start_time: If provided, lower bound on last_modified_timestamp.
54
+ end_time: If provided, upper bound on last_modified_timestamp.
55
+ include_expired: When ``False`` (default), rows whose
56
+ ``expiration_timestamp`` is in the past are excluded
57
+ (byte-for-byte prior behaviour — every existing caller is
58
+ unaffected). Pass ``True`` to omit the expiry filter so
59
+ EXPIRED tombstones (``expiration_timestamp < now``) are
60
+ returned. Required by ``clear_user_data`` to reach every
61
+ profile a user owns regardless of TTL state.
62
+
63
+ Returns:
64
+ list[UserProfile]: Matching profiles in unspecified order.
64
65
  """
65
66
  raise NotImplementedError
66
67
 
67
- # delete methods
68
68
  @abstractmethod
69
- def delete_user_interaction(self, request: DeleteUserInteractionRequest) -> None:
69
+ def add_user_profile(self, user_id: str, user_profiles: list[UserProfile]) -> None:
70
+ """Add the user profile for a given user id."""
70
71
  raise NotImplementedError
71
72
 
72
73
  @abstractmethod
@@ -86,10 +87,6 @@ class ProfileMixin:
86
87
  """Replace only the tags of a profile, leaving content and embedding untouched."""
87
88
  raise NotImplementedError
88
89
 
89
- @abstractmethod
90
- def delete_all_interactions_for_user(self, user_id: str) -> None:
91
- raise NotImplementedError
92
-
93
90
  @abstractmethod
94
91
  def delete_all_profiles_for_user(self, user_id: str) -> None:
95
92
  raise NotImplementedError
@@ -99,20 +96,6 @@ class ProfileMixin:
99
96
  """Delete all profiles across all users."""
100
97
  raise NotImplementedError
101
98
 
102
- @abstractmethod
103
- def delete_all_interactions(self) -> None:
104
- """Delete all interactions across all users."""
105
- raise NotImplementedError
106
-
107
- @abstractmethod
108
- def count_all_interactions(self) -> int:
109
- """Count total interactions across all users.
110
-
111
- Returns:
112
- int: Total number of interactions
113
- """
114
- raise NotImplementedError
115
-
116
99
  @abstractmethod
117
100
  def count_all_profiles(self) -> int:
118
101
  """Count total profiles across all users without hydrating rows.
@@ -127,18 +110,6 @@ class ProfileMixin:
127
110
  """
128
111
  raise NotImplementedError
129
112
 
130
- @abstractmethod
131
- def delete_oldest_interactions(self, count: int) -> int:
132
- """Delete the oldest N interactions based on created_at timestamp.
133
-
134
- Args:
135
- count (int): Number of oldest interactions to delete
136
-
137
- Returns:
138
- int: Number of interactions actually deleted
139
- """
140
- raise NotImplementedError
141
-
142
113
  @abstractmethod
143
114
  def update_all_profiles_status(
144
115
  self,
@@ -158,6 +129,24 @@ class ProfileMixin:
158
129
  """
159
130
  raise NotImplementedError
160
131
 
132
+ @abstractmethod
133
+ def expire_active_profiles(self, *, now: int, limit: int = 1000) -> int:
134
+ """Tombstone active profiles whose TTL has elapsed.
135
+
136
+ Selects ``status IS NULL AND expiration_timestamp < now`` and transitions
137
+ each to ``status=EXPIRED, retired_at=now``, emitting a ``status_change``
138
+ lineage event (reason ``ttl-expired``). Returns the number tombstoned.
139
+
140
+ Args:
141
+ now: Current epoch timestamp (seconds). Profiles with
142
+ ``expiration_timestamp < now`` are tombstoned.
143
+ limit: Maximum number of profiles to tombstone in one call (default 1000).
144
+
145
+ Returns:
146
+ int: Number of profiles tombstoned.
147
+ """
148
+ raise NotImplementedError
149
+
161
150
  @abstractmethod
162
151
  def get_profiles_by_ids(
163
152
  self,
@@ -350,21 +339,3 @@ class ProfileMixin:
350
339
  commit-atomic legacy change-log "removed" entry.
351
340
  """
352
341
  raise NotImplementedError
353
-
354
- # Search methods
355
- @abstractmethod
356
- def search_interaction(
357
- self,
358
- search_interaction_request: SearchInteractionRequest,
359
- query_embedding: list[float] | None = None,
360
- ) -> list[Interaction]:
361
- raise NotImplementedError
362
-
363
- @abstractmethod
364
- def search_user_profile(
365
- self,
366
- search_user_profile_request: SearchUserProfileRequest,
367
- status_filter: list[Status | None] | None = None,
368
- query_embedding: list[float] | None = None,
369
- ) -> list[UserProfile]:
370
- raise NotImplementedError