minder-cli 0.2.0__py3-none-any.whl

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 (132) hide show
  1. minder/__init__.py +12 -0
  2. minder/api/routers/prompts.py +177 -0
  3. minder/application/__init__.py +1 -0
  4. minder/application/admin/__init__.py +11 -0
  5. minder/application/admin/dto.py +453 -0
  6. minder/application/admin/jobs.py +327 -0
  7. minder/application/admin/use_cases.py +1895 -0
  8. minder/auth/__init__.py +12 -0
  9. minder/auth/context.py +26 -0
  10. minder/auth/middleware.py +70 -0
  11. minder/auth/principal.py +59 -0
  12. minder/auth/rate_limiter.py +89 -0
  13. minder/auth/rbac.py +60 -0
  14. minder/auth/service.py +541 -0
  15. minder/bootstrap/__init__.py +9 -0
  16. minder/bootstrap/providers.py +109 -0
  17. minder/bootstrap/transport.py +807 -0
  18. minder/cache/__init__.py +10 -0
  19. minder/cache/providers.py +140 -0
  20. minder/chunking/__init__.py +4 -0
  21. minder/chunking/code_splitter.py +184 -0
  22. minder/chunking/splitter.py +136 -0
  23. minder/cli.py +1542 -0
  24. minder/config.py +179 -0
  25. minder/continuity.py +363 -0
  26. minder/dev.py +160 -0
  27. minder/embedding/__init__.py +9 -0
  28. minder/embedding/base.py +7 -0
  29. minder/embedding/local.py +65 -0
  30. minder/embedding/openai.py +7 -0
  31. minder/graph/__init__.py +11 -0
  32. minder/graph/edges.py +13 -0
  33. minder/graph/executor.py +127 -0
  34. minder/graph/graph.py +263 -0
  35. minder/graph/nodes/__init__.py +27 -0
  36. minder/graph/nodes/evaluator.py +21 -0
  37. minder/graph/nodes/guard.py +64 -0
  38. minder/graph/nodes/llm.py +59 -0
  39. minder/graph/nodes/planning.py +30 -0
  40. minder/graph/nodes/reasoning.py +87 -0
  41. minder/graph/nodes/reranker.py +141 -0
  42. minder/graph/nodes/retriever.py +86 -0
  43. minder/graph/nodes/verification.py +230 -0
  44. minder/graph/nodes/workflow_planner.py +250 -0
  45. minder/graph/runtime.py +15 -0
  46. minder/graph/state.py +26 -0
  47. minder/llm/__init__.py +5 -0
  48. minder/llm/base.py +14 -0
  49. minder/llm/local.py +381 -0
  50. minder/llm/openai.py +89 -0
  51. minder/models/__init__.py +109 -0
  52. minder/models/base.py +10 -0
  53. minder/models/client.py +137 -0
  54. minder/models/document.py +34 -0
  55. minder/models/error.py +32 -0
  56. minder/models/graph.py +114 -0
  57. minder/models/history.py +32 -0
  58. minder/models/job.py +62 -0
  59. minder/models/prompt.py +41 -0
  60. minder/models/repository.py +62 -0
  61. minder/models/rule.py +68 -0
  62. minder/models/session.py +51 -0
  63. minder/models/skill.py +52 -0
  64. minder/models/user.py +41 -0
  65. minder/models/workflow.py +35 -0
  66. minder/observability/__init__.py +57 -0
  67. minder/observability/audit.py +243 -0
  68. minder/observability/logging.py +253 -0
  69. minder/observability/metrics.py +448 -0
  70. minder/observability/tracing.py +215 -0
  71. minder/presentation/__init__.py +1 -0
  72. minder/presentation/http/__init__.py +1 -0
  73. minder/presentation/http/admin/__init__.py +3 -0
  74. minder/presentation/http/admin/api.py +1309 -0
  75. minder/presentation/http/admin/context.py +94 -0
  76. minder/presentation/http/admin/dashboard.py +111 -0
  77. minder/presentation/http/admin/jobs.py +208 -0
  78. minder/presentation/http/admin/memories.py +185 -0
  79. minder/presentation/http/admin/prompts.py +219 -0
  80. minder/presentation/http/admin/routes.py +127 -0
  81. minder/presentation/http/admin/runtime.py +650 -0
  82. minder/presentation/http/admin/search.py +368 -0
  83. minder/presentation/http/admin/skills.py +230 -0
  84. minder/prompts/__init__.py +646 -0
  85. minder/prompts/formatter.py +142 -0
  86. minder/resources/__init__.py +318 -0
  87. minder/retrieval/__init__.py +5 -0
  88. minder/retrieval/hybrid.py +178 -0
  89. minder/retrieval/mmr.py +116 -0
  90. minder/retrieval/multi_hop.py +115 -0
  91. minder/runtime.py +15 -0
  92. minder/server.py +145 -0
  93. minder/store/__init__.py +64 -0
  94. minder/store/document.py +115 -0
  95. minder/store/error.py +82 -0
  96. minder/store/feedback.py +114 -0
  97. minder/store/graph.py +588 -0
  98. minder/store/history.py +57 -0
  99. minder/store/interfaces.py +512 -0
  100. minder/store/milvus/__init__.py +11 -0
  101. minder/store/milvus/client.py +26 -0
  102. minder/store/milvus/collections.py +15 -0
  103. minder/store/milvus/vector_store.py +232 -0
  104. minder/store/mongodb/__init__.py +11 -0
  105. minder/store/mongodb/client.py +49 -0
  106. minder/store/mongodb/indexes.py +90 -0
  107. minder/store/mongodb/operational_store.py +993 -0
  108. minder/store/relational.py +1087 -0
  109. minder/store/repo_state.py +58 -0
  110. minder/store/rule.py +93 -0
  111. minder/store/vector.py +79 -0
  112. minder/tools/__init__.py +47 -0
  113. minder/tools/auth.py +94 -0
  114. minder/tools/graph.py +839 -0
  115. minder/tools/ingest.py +353 -0
  116. minder/tools/memory.py +381 -0
  117. minder/tools/query.py +307 -0
  118. minder/tools/registry.py +269 -0
  119. minder/tools/repo_scanner.py +1266 -0
  120. minder/tools/search.py +15 -0
  121. minder/tools/session.py +316 -0
  122. minder/tools/skills.py +899 -0
  123. minder/tools/workflow.py +215 -0
  124. minder/transport/__init__.py +4 -0
  125. minder/transport/base.py +286 -0
  126. minder/transport/sse.py +252 -0
  127. minder/transport/stdio.py +29 -0
  128. minder_cli-0.2.0.dist-info/METADATA +318 -0
  129. minder_cli-0.2.0.dist-info/RECORD +132 -0
  130. minder_cli-0.2.0.dist-info/WHEEL +4 -0
  131. minder_cli-0.2.0.dist-info/entry_points.txt +2 -0
  132. minder_cli-0.2.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ import uuid
4
+ from typing import Any
5
+
6
+ from sqlalchemy import select
7
+
8
+ from minder.models.history import History
9
+ from minder.models.session import Session
10
+ from minder.store.relational import RelationalStore
11
+
12
+
13
+ class HistoryStore:
14
+ def __init__(self, store: RelationalStore) -> None:
15
+ self._store = store
16
+
17
+ async def create_history(
18
+ self,
19
+ session_id: uuid.UUID,
20
+ role: str,
21
+ content: str,
22
+ reasoning_trace: str | None = None,
23
+ tool_calls: dict[str, Any] | None = None,
24
+ tokens_used: int = 0,
25
+ latency_ms: int = 0,
26
+ ) -> History:
27
+ async with self._store._session() as sess:
28
+ history = History(
29
+ id=uuid.uuid4(),
30
+ session_id=session_id,
31
+ role=role,
32
+ content=content,
33
+ reasoning_trace=reasoning_trace,
34
+ tool_calls=tool_calls or {},
35
+ tokens_used=tokens_used,
36
+ latency_ms=latency_ms,
37
+ )
38
+ sess.add(history)
39
+ await sess.flush()
40
+ await sess.refresh(history)
41
+ return history
42
+
43
+ async def list_history_for_session(self, session_id: uuid.UUID) -> list[History]:
44
+ async with self._store._session() as sess:
45
+ result = await sess.execute(
46
+ select(History).where(History.session_id == session_id)
47
+ )
48
+ return list(result.scalars().all())
49
+
50
+ async def list_history_for_user(self, user_id: uuid.UUID) -> list[History]:
51
+ async with self._store._session() as sess:
52
+ result = await sess.execute(
53
+ select(History)
54
+ .join(Session, Session.id == History.session_id)
55
+ .where(Session.user_id == user_id)
56
+ )
57
+ return list(result.scalars().all())
@@ -0,0 +1,512 @@
1
+ """
2
+ Domain Repository Interfaces — Clean Architecture boundary.
3
+
4
+ These Protocol classes define the contracts that all store adapters
5
+ (SQLite, MongoDB, etc.) must satisfy. The application layer depends
6
+ only on these interfaces, never on concrete implementations.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from datetime import datetime
12
+ import uuid
13
+ from typing import Any, Protocol, runtime_checkable
14
+
15
+
16
+ # ---------------------------------------------------------------------------
17
+ # User Repository
18
+ # ---------------------------------------------------------------------------
19
+
20
+
21
+ @runtime_checkable
22
+ class IUserRepository(Protocol):
23
+ async def create_user(self, **kwargs: Any) -> Any: ...
24
+ async def get_user_by_id(self, user_id: uuid.UUID) -> Any | None: ...
25
+ async def get_user_by_email(self, email: str) -> Any | None: ...
26
+ async def get_user_by_username(self, username: str) -> Any | None: ...
27
+ async def list_users(self, active_only: bool = True) -> list[Any]: ...
28
+ async def update_user(self, user_id: uuid.UUID, **kwargs: Any) -> Any | None: ...
29
+ async def delete_user(self, user_id: uuid.UUID) -> None: ...
30
+ async def has_admin_users(self) -> bool: ...
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Skill Repository
35
+ # ---------------------------------------------------------------------------
36
+
37
+
38
+ @runtime_checkable
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Prompt Repository
42
+ # ---------------------------------------------------------------------------
43
+
44
+
45
+ @runtime_checkable
46
+ class IPromptRepository(Protocol):
47
+ async def create_prompt(self, **kwargs: Any) -> Any: ...
48
+ async def get_prompt_by_id(self, prompt_id: uuid.UUID) -> Any | None: ...
49
+ async def get_prompt_by_name(self, name: str) -> Any | None: ...
50
+ async def list_prompts(self) -> list[Any]: ...
51
+ async def update_prompt(
52
+ self, prompt_id: uuid.UUID, **kwargs: Any
53
+ ) -> Any | None: ...
54
+ async def delete_prompt(self, prompt_id: uuid.UUID) -> None: ...
55
+
56
+
57
+ @runtime_checkable
58
+ class ISkillRepository(Protocol):
59
+ async def create_skill(self, **kwargs: Any) -> Any: ...
60
+ async def get_skill_by_id(self, skill_id: uuid.UUID) -> Any | None: ...
61
+ async def list_skills(self) -> list[Any]: ...
62
+ async def update_skill(self, skill_id: uuid.UUID, **kwargs: Any) -> Any | None: ...
63
+ async def delete_skill(self, skill_id: uuid.UUID) -> None: ...
64
+
65
+
66
+ # ---------------------------------------------------------------------------
67
+ # Session Repository
68
+ # ---------------------------------------------------------------------------
69
+
70
+
71
+ @runtime_checkable
72
+ class ISessionRepository(Protocol):
73
+ async def create_session(self, **kwargs: Any) -> Any: ...
74
+ async def get_session_by_id(self, session_id: uuid.UUID) -> Any | None: ...
75
+ async def get_sessions_by_user(self, user_id: uuid.UUID) -> list[Any]: ...
76
+ async def get_sessions_by_client(self, client_id: uuid.UUID) -> list[Any]: ...
77
+ async def find_session_by_name(
78
+ self,
79
+ name: str,
80
+ *,
81
+ user_id: uuid.UUID | None = None,
82
+ client_id: uuid.UUID | None = None,
83
+ ) -> Any | None: ...
84
+ async def update_session(
85
+ self, session_id: uuid.UUID, **kwargs: Any
86
+ ) -> Any | None: ...
87
+ async def delete_session(self, session_id: uuid.UUID) -> None: ...
88
+ async def cleanup_expired_sessions(
89
+ self,
90
+ *,
91
+ now: datetime | None = None,
92
+ user_id: uuid.UUID | None = None,
93
+ client_id: uuid.UUID | None = None,
94
+ ) -> dict[str, int]: ...
95
+
96
+
97
+ # ---------------------------------------------------------------------------
98
+ # Workflow Repository
99
+ # ---------------------------------------------------------------------------
100
+
101
+
102
+ @runtime_checkable
103
+ class IWorkflowRepository(Protocol):
104
+ async def create_workflow(self, **kwargs: Any) -> Any: ...
105
+ async def get_workflow_by_id(self, workflow_id: uuid.UUID) -> Any | None: ...
106
+ async def get_workflow_by_name(self, name: str) -> Any | None: ...
107
+ async def list_workflows(self) -> list[Any]: ...
108
+ async def update_workflow(
109
+ self, workflow_id: uuid.UUID, **kwargs: Any
110
+ ) -> Any | None: ...
111
+ async def delete_workflow(self, workflow_id: uuid.UUID) -> None: ...
112
+
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # Repository (code repository) Repository
116
+ # ---------------------------------------------------------------------------
117
+
118
+
119
+ @runtime_checkable
120
+ class IRepositoryRepo(Protocol):
121
+ async def create_repository(self, **kwargs: Any) -> Any: ...
122
+ async def get_repository_by_id(self, repo_id: uuid.UUID) -> Any | None: ...
123
+ async def get_repository_by_name(self, repo_name: str) -> Any | None: ...
124
+ async def list_repositories(self) -> list[Any]: ...
125
+ async def update_repository(
126
+ self, repo_id: uuid.UUID, **kwargs: Any
127
+ ) -> Any | None: ...
128
+ async def delete_repository(self, repo_id: uuid.UUID) -> None: ...
129
+
130
+
131
+ # ---------------------------------------------------------------------------
132
+ # Workflow State Repository
133
+ # ---------------------------------------------------------------------------
134
+
135
+
136
+ @runtime_checkable
137
+ class IWorkflowStateRepository(Protocol):
138
+ async def create_workflow_state(self, **kwargs: Any) -> Any: ...
139
+ async def get_workflow_state_by_id(self, state_id: uuid.UUID) -> Any | None: ...
140
+ async def get_workflow_state_by_repo(self, repo_id: uuid.UUID) -> Any | None: ...
141
+ async def update_workflow_state(
142
+ self, state_id: uuid.UUID, **kwargs: Any
143
+ ) -> Any | None: ...
144
+ async def delete_workflow_state(self, state_id: uuid.UUID) -> None: ...
145
+
146
+
147
+ # ---------------------------------------------------------------------------
148
+ # Document Repository
149
+ # ---------------------------------------------------------------------------
150
+
151
+
152
+ @runtime_checkable
153
+ class IDocumentRepository(Protocol):
154
+ async def create_document(
155
+ self,
156
+ title: str,
157
+ content: str,
158
+ doc_type: str,
159
+ source_path: str,
160
+ project: str,
161
+ *,
162
+ chunks: dict[str, Any] | None = None,
163
+ embedding: list[float] | None = None,
164
+ ) -> Any: ...
165
+
166
+ async def get_document_by_path(
167
+ self, source_path: str, *, project: str | None = None
168
+ ) -> Any | None: ...
169
+
170
+ async def get_documents_by_ids(self, doc_ids: list[uuid.UUID]) -> list[Any]: ...
171
+
172
+ async def list_documents(self, project: str | None = None) -> list[Any]: ...
173
+
174
+ async def upsert_document(
175
+ self,
176
+ *,
177
+ title: str,
178
+ content: str,
179
+ doc_type: str,
180
+ source_path: str,
181
+ project: str,
182
+ chunks: dict[str, Any] | None = None,
183
+ embedding: list[float] | None = None,
184
+ ) -> Any: ...
185
+
186
+ async def delete_documents_not_in_paths(
187
+ self, *, project: str, keep_paths: set[str]
188
+ ) -> None: ...
189
+
190
+
191
+ # ---------------------------------------------------------------------------
192
+ # History Repository
193
+ # ---------------------------------------------------------------------------
194
+
195
+
196
+ @runtime_checkable
197
+ class IHistoryRepository(Protocol):
198
+ async def create_history(
199
+ self,
200
+ session_id: uuid.UUID,
201
+ role: str,
202
+ content: str,
203
+ reasoning_trace: str | None = None,
204
+ tool_calls: dict[str, Any] | None = None,
205
+ tokens_used: int = 0,
206
+ latency_ms: int = 0,
207
+ ) -> Any: ...
208
+
209
+ async def list_history_for_session(self, session_id: uuid.UUID) -> list[Any]: ...
210
+ async def list_history_for_user(self, user_id: uuid.UUID) -> list[Any]: ...
211
+ async def delete_history_for_session(self, session_id: uuid.UUID) -> int: ...
212
+
213
+
214
+ # ---------------------------------------------------------------------------
215
+ # Error Repository
216
+ # ---------------------------------------------------------------------------
217
+
218
+
219
+ @runtime_checkable
220
+ class IErrorRepository(Protocol):
221
+ async def create_error(
222
+ self,
223
+ error_code: str,
224
+ error_message: str,
225
+ stack_trace: str | None = None,
226
+ context: dict[str, Any] | None = None,
227
+ resolution: str | None = None,
228
+ embedding: list[float] | None = None,
229
+ resolved: bool = False,
230
+ ) -> Any: ...
231
+
232
+ async def list_errors(self) -> list[Any]: ...
233
+ async def search_errors(
234
+ self, query: str, limit: int = 5
235
+ ) -> list[dict[str, Any]]: ...
236
+
237
+
238
+ # ---------------------------------------------------------------------------
239
+ # Rule Repository
240
+ # ---------------------------------------------------------------------------
241
+
242
+
243
+ @runtime_checkable
244
+ class IRuleRepository(Protocol):
245
+ async def create_rule(self, **kwargs: Any) -> Any: ...
246
+ async def get_rule_by_id(self, rule_id: uuid.UUID) -> Any | None: ...
247
+ async def list_rules(self) -> list[Any]: ...
248
+ async def list_by_scope(self, scope: str) -> list[Any]: ...
249
+ async def list_active(self) -> list[Any]: ...
250
+ async def update_rule(self, rule_id: uuid.UUID, **kwargs: Any) -> Any | None: ...
251
+ async def delete_rule(self, rule_id: uuid.UUID) -> None: ...
252
+
253
+
254
+ # ---------------------------------------------------------------------------
255
+ # Feedback Repository
256
+ # ---------------------------------------------------------------------------
257
+
258
+
259
+ @runtime_checkable
260
+ class IFeedbackRepository(Protocol):
261
+ async def create_feedback(self, **kwargs: Any) -> Any: ...
262
+ async def get_feedback_by_id(self, feedback_id: uuid.UUID) -> Any | None: ...
263
+ async def list_feedback(self) -> list[Any]: ...
264
+ async def list_by_entity(
265
+ self, entity_type: str, entity_id: uuid.UUID
266
+ ) -> list[Any]: ...
267
+ async def average_rating(self, entity_id: uuid.UUID) -> float | None: ...
268
+ async def update_feedback(
269
+ self, feedback_id: uuid.UUID, **kwargs: Any
270
+ ) -> Any | None: ...
271
+ async def delete_feedback(self, feedback_id: uuid.UUID) -> None: ...
272
+
273
+
274
+ # ---------------------------------------------------------------------------
275
+ # Knowledge Graph Repository
276
+ # ---------------------------------------------------------------------------
277
+
278
+
279
+ @runtime_checkable
280
+ class IGraphRepository(Protocol):
281
+ async def add_node(
282
+ self,
283
+ node_type: str,
284
+ name: str,
285
+ metadata: dict[str, Any] | None = None,
286
+ node_id: uuid.UUID | None = None,
287
+ *,
288
+ repo_id: str = "",
289
+ branch: str = "",
290
+ ) -> Any: ...
291
+ async def upsert_node(
292
+ self,
293
+ node_type: str,
294
+ name: str,
295
+ metadata: dict[str, Any] | None = None,
296
+ *,
297
+ repo_id: str = "",
298
+ branch: str = "",
299
+ ) -> Any: ...
300
+ async def get_node(self, node_id: uuid.UUID) -> Any | None: ...
301
+ async def get_node_by_name(
302
+ self,
303
+ node_type: str,
304
+ name: str,
305
+ *,
306
+ repo_id: str = "",
307
+ branch: str = "",
308
+ ) -> Any | None: ...
309
+ async def list_nodes(self) -> list[Any]: ...
310
+ async def list_nodes_by_scope(
311
+ self, *, repo_id: str, branch: str | None = None
312
+ ) -> list[Any]: ...
313
+ async def list_edges(self) -> list[Any]: ...
314
+ async def list_edges_by_scope(self, *, repo_id: str) -> list[Any]: ...
315
+ async def query_by_type(
316
+ self, node_type: str, *, repo_id: str = ""
317
+ ) -> list[Any]: ...
318
+ async def delete_node(self, node_id: uuid.UUID) -> None: ...
319
+ async def delete_nodes_by_scope(
320
+ self,
321
+ *,
322
+ repo_id: str,
323
+ branch: str | None = None,
324
+ paths: set[str] | None = None,
325
+ ) -> int: ...
326
+ async def add_edge(
327
+ self,
328
+ source_id: uuid.UUID,
329
+ target_id: uuid.UUID,
330
+ relation: str,
331
+ weight: float = 1.0,
332
+ edge_id: uuid.UUID | None = None,
333
+ *,
334
+ repo_id: str = "",
335
+ ) -> Any: ...
336
+ async def upsert_edge(
337
+ self,
338
+ source_id: uuid.UUID,
339
+ target_id: uuid.UUID,
340
+ relation: str,
341
+ weight: float = 1.0,
342
+ *,
343
+ repo_id: str = "",
344
+ ) -> Any: ...
345
+ async def delete_edge(self, edge_id: uuid.UUID) -> None: ...
346
+ async def get_neighbors(
347
+ self,
348
+ node_id: uuid.UUID,
349
+ *,
350
+ direction: str = "out",
351
+ relation: str | None = None,
352
+ ) -> list[Any]: ...
353
+ async def get_path(
354
+ self,
355
+ source_id: uuid.UUID,
356
+ target_id: uuid.UUID,
357
+ *,
358
+ max_depth: int = 6,
359
+ ) -> list[Any]: ...
360
+
361
+
362
+ # ---------------------------------------------------------------------------
363
+ # Client Gateway Repository
364
+ # ---------------------------------------------------------------------------
365
+
366
+
367
+ @runtime_checkable
368
+ class IClientRepository(Protocol):
369
+ async def create_client(self, **kwargs: Any) -> Any: ...
370
+ async def get_client_by_id(self, client_id: uuid.UUID) -> Any | None: ...
371
+ async def get_client_by_slug(self, slug: str) -> Any | None: ...
372
+ async def list_clients(self) -> list[Any]: ...
373
+ async def update_client(
374
+ self, client_id: uuid.UUID, **kwargs: Any
375
+ ) -> Any | None: ...
376
+ async def create_client_api_key(self, **kwargs: Any) -> Any: ...
377
+ async def list_client_api_keys(self, client_id: uuid.UUID) -> list[Any]: ...
378
+ async def update_client_api_key(
379
+ self, key_id: uuid.UUID, **kwargs: Any
380
+ ) -> Any | None: ...
381
+ async def create_client_session(self, **kwargs: Any) -> Any: ...
382
+ async def count_active_client_sessions(self) -> int: ...
383
+ async def get_client_session_by_token_id(self, token_id: str) -> Any | None: ...
384
+ async def update_client_session(
385
+ self, session_id: uuid.UUID, **kwargs: Any
386
+ ) -> Any | None: ...
387
+ async def create_audit_log(self, **kwargs: Any) -> Any: ...
388
+ async def list_audit_logs(
389
+ self,
390
+ *,
391
+ actor_id: str | None = None,
392
+ event_type: str | None = None,
393
+ outcome: str | None = None,
394
+ limit: int | None = None,
395
+ offset: int = 0,
396
+ ) -> list[Any]: ...
397
+ async def count_audit_logs(
398
+ self,
399
+ *,
400
+ actor_id: str | None = None,
401
+ event_type: str | None = None,
402
+ outcome: str | None = None,
403
+ ) -> int: ...
404
+ async def get_audit_summary(
405
+ self,
406
+ *,
407
+ actor_id: str | None = None,
408
+ event_type: str | None = None,
409
+ outcome: str | None = None,
410
+ group_by: str = "event_type",
411
+ ) -> dict[str, int]: ...
412
+
413
+
414
+ @runtime_checkable
415
+ class IAdminJobRepository(Protocol):
416
+ async def create_admin_job(self, **kwargs: Any) -> Any: ...
417
+ async def get_admin_job_by_id(self, job_id: uuid.UUID) -> Any | None: ...
418
+ async def list_admin_jobs(
419
+ self,
420
+ *,
421
+ job_type: str | None = None,
422
+ status: str | None = None,
423
+ requested_by_user_id: uuid.UUID | None = None,
424
+ limit: int | None = None,
425
+ offset: int = 0,
426
+ ) -> list[Any]: ...
427
+ async def update_admin_job(
428
+ self, job_id: uuid.UUID, **kwargs: Any
429
+ ) -> Any | None: ...
430
+
431
+
432
+ # ---------------------------------------------------------------------------
433
+ # Cache Provider (for Redis)
434
+ # ---------------------------------------------------------------------------
435
+
436
+
437
+ @runtime_checkable
438
+ class ICacheProvider(Protocol):
439
+ async def get(self, key: str) -> str | None: ...
440
+ async def set(self, key: str, value: str, *, ttl: int | None = None) -> None: ...
441
+ async def delete(self, key: str) -> None: ...
442
+ async def exists(self, key: str) -> bool: ...
443
+ async def expire(self, key: str, ttl: int) -> None: ...
444
+ async def incr(self, key: str) -> int: ...
445
+ async def keys(self, pattern: str) -> list[str]: ...
446
+ async def flush_namespace(self, namespace: str) -> None: ...
447
+ async def health_check(self) -> bool: ...
448
+ async def close(self) -> None: ...
449
+
450
+
451
+ # ---------------------------------------------------------------------------
452
+ # Vector Store
453
+ # ---------------------------------------------------------------------------
454
+
455
+
456
+ @runtime_checkable
457
+ class IVectorStore(Protocol):
458
+ async def upsert_document(
459
+ self,
460
+ doc_id: uuid.UUID,
461
+ embedding: list[float],
462
+ payload: dict[str, Any],
463
+ ) -> None: ...
464
+
465
+ async def delete_documents(self, doc_ids: list[uuid.UUID]) -> None: ...
466
+
467
+ async def search_documents(
468
+ self,
469
+ query_embedding: list[float],
470
+ *,
471
+ project: str | None = None,
472
+ doc_types: set[str] | None = None,
473
+ limit: int = 5,
474
+ score_threshold: float = 0.0,
475
+ ) -> list[dict[str, Any]]: ...
476
+
477
+ async def setup(self) -> None: ...
478
+
479
+
480
+ # ---------------------------------------------------------------------------
481
+ # Operational Store — composite interface for backwards compatibility
482
+ # ---------------------------------------------------------------------------
483
+
484
+
485
+ @runtime_checkable
486
+ class IOperationalStore(
487
+ IUserRepository,
488
+ ISkillRepository,
489
+ IPromptRepository,
490
+ ISessionRepository,
491
+ IWorkflowRepository,
492
+ IRepositoryRepo,
493
+ IWorkflowStateRepository,
494
+ IDocumentRepository,
495
+ IHistoryRepository,
496
+ IErrorRepository,
497
+ IRuleRepository,
498
+ IFeedbackRepository,
499
+ IClientRepository,
500
+ IAdminJobRepository,
501
+ Protocol,
502
+ ):
503
+ """
504
+ Composite interface matching the current RelationalStore surface.
505
+
506
+ This allows existing code that depends on a single store object
507
+ (e.g., server.py, tools) to continue working while we migrate
508
+ individual repositories to MongoDB behind the scenes.
509
+ """
510
+
511
+ async def init_db(self) -> None: ...
512
+ async def dispose(self) -> None: ...
@@ -0,0 +1,11 @@
1
+ """
2
+ Milvus Store Package — Standalone Milvus implementations.
3
+ """
4
+
5
+ from minder.store.milvus.client import MilvusClient
6
+ from minder.store.milvus.vector_store import MilvusVectorStore
7
+
8
+ __all__ = [
9
+ "MilvusClient",
10
+ "MilvusVectorStore",
11
+ ]
@@ -0,0 +1,26 @@
1
+ """
2
+ Milvus Client — connection wrapper.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ from pymilvus import MilvusClient as PyMilvusClient # type: ignore[import-untyped]
9
+
10
+
11
+ class MilvusClient:
12
+ """Wrapper around PyMilvus client (which is blocking, so we run it in executor if needed)."""
13
+
14
+ def __init__(self, uri: str = "http://localhost:19530") -> None:
15
+ self.uri = uri
16
+ self.client = PyMilvusClient(uri=uri)
17
+
18
+ async def health_check(self) -> bool:
19
+ """Pings the Milvus server asynchronously."""
20
+ loop = asyncio.get_running_loop()
21
+ try:
22
+ # We list collections as a proxy for health check
23
+ await loop.run_in_executor(None, self.client.list_collections)
24
+ return True
25
+ except Exception:
26
+ return False
@@ -0,0 +1,15 @@
1
+ """
2
+ Milvus Collections — schema definitions.
3
+ """
4
+ from pymilvus import CollectionSchema, DataType, FieldSchema # type: ignore[import-untyped]
5
+
6
+ def get_document_schema(dimensions: int = 768) -> CollectionSchema:
7
+ fields = [
8
+ FieldSchema(name="id", dtype=DataType.VARCHAR, max_length=64, is_primary=True),
9
+ FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=dimensions),
10
+ FieldSchema(name="project", dtype=DataType.VARCHAR, max_length=256),
11
+ FieldSchema(name="doc_type", dtype=DataType.VARCHAR, max_length=64),
12
+ # Payload stored as JSON string because Milvus JSON handles unstructured data
13
+ FieldSchema(name="payload", dtype=DataType.JSON),
14
+ ]
15
+ return CollectionSchema(fields, description="Document chunks for RAG")