atomicmemory 1.0.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.
- atomicmemory/__init__.py +166 -0
- atomicmemory/_version.py +3 -0
- atomicmemory/client/__init__.py +22 -0
- atomicmemory/client/async_memory_client.py +202 -0
- atomicmemory/client/atomic_memory_client.py +181 -0
- atomicmemory/client/memory_client.py +292 -0
- atomicmemory/core/__init__.py +34 -0
- atomicmemory/core/errors.py +122 -0
- atomicmemory/core/events.py +65 -0
- atomicmemory/core/logging.py +37 -0
- atomicmemory/core/retry.py +124 -0
- atomicmemory/core/validation.py +22 -0
- atomicmemory/embeddings/__init__.py +16 -0
- atomicmemory/embeddings/base.py +39 -0
- atomicmemory/embeddings/sentence_transformers.py +104 -0
- atomicmemory/kv_cache/__init__.py +17 -0
- atomicmemory/kv_cache/adapter.py +50 -0
- atomicmemory/kv_cache/memory_storage.py +98 -0
- atomicmemory/kv_cache/sqlite_storage.py +122 -0
- atomicmemory/memory/__init__.py +82 -0
- atomicmemory/memory/filters.py +68 -0
- atomicmemory/memory/pipeline.py +42 -0
- atomicmemory/memory/provider.py +397 -0
- atomicmemory/memory/registry.py +95 -0
- atomicmemory/memory/service.py +199 -0
- atomicmemory/memory/types.py +398 -0
- atomicmemory/providers/__init__.py +5 -0
- atomicmemory/providers/atomicmemory/__init__.py +43 -0
- atomicmemory/providers/atomicmemory/agents.py +156 -0
- atomicmemory/providers/atomicmemory/async_handle_impl.py +198 -0
- atomicmemory/providers/atomicmemory/async_provider.py +245 -0
- atomicmemory/providers/atomicmemory/audit.py +74 -0
- atomicmemory/providers/atomicmemory/config.py +38 -0
- atomicmemory/providers/atomicmemory/config_handle.py +123 -0
- atomicmemory/providers/atomicmemory/handle.py +513 -0
- atomicmemory/providers/atomicmemory/handle_impl.py +325 -0
- atomicmemory/providers/atomicmemory/http.py +255 -0
- atomicmemory/providers/atomicmemory/lessons.py +133 -0
- atomicmemory/providers/atomicmemory/lifecycle.py +202 -0
- atomicmemory/providers/atomicmemory/mappers.py +125 -0
- atomicmemory/providers/atomicmemory/path.py +20 -0
- atomicmemory/providers/atomicmemory/provider.py +300 -0
- atomicmemory/providers/atomicmemory/scope_mapper.py +98 -0
- atomicmemory/providers/mem0/__init__.py +41 -0
- atomicmemory/providers/mem0/async_provider.py +191 -0
- atomicmemory/providers/mem0/config.py +51 -0
- atomicmemory/providers/mem0/http.py +195 -0
- atomicmemory/providers/mem0/mappers.py +145 -0
- atomicmemory/providers/mem0/provider.py +202 -0
- atomicmemory/py.typed +0 -0
- atomicmemory/search/__init__.py +47 -0
- atomicmemory/search/chunking.py +161 -0
- atomicmemory/search/ranking.py +94 -0
- atomicmemory/search/semantic_search.py +130 -0
- atomicmemory/search/similarity.py +110 -0
- atomicmemory/storage/__init__.py +63 -0
- atomicmemory/storage/_mapping.py +305 -0
- atomicmemory/storage/async_client.py +208 -0
- atomicmemory/storage/client.py +339 -0
- atomicmemory/storage/errors.py +115 -0
- atomicmemory/storage/types.py +305 -0
- atomicmemory/utils/__init__.py +5 -0
- atomicmemory/utils/environment.py +23 -0
- atomicmemory-1.0.0.dist-info/METADATA +146 -0
- atomicmemory-1.0.0.dist-info/RECORD +67 -0
- atomicmemory-1.0.0.dist-info/WHEEL +4 -0
- atomicmemory-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"""V3 memory types.
|
|
2
|
+
|
|
3
|
+
Port of `atomicmemory-sdk/src/memory/types.ts`. Pydantic v2 models with
|
|
4
|
+
snake_case field names; wire-format translation (e.g. core's `user_id`
|
|
5
|
+
or `created_at`) lives in each provider's `mappers.py`.
|
|
6
|
+
|
|
7
|
+
The discriminated union `IngestInput` is modeled with Pydantic's
|
|
8
|
+
`discriminator="mode"` so callers can pass plain dicts and validation
|
|
9
|
+
picks the right variant.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from typing import Annotated, Any, Literal
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictBool
|
|
18
|
+
|
|
19
|
+
from atomicmemory.memory.filters import FieldFilterOp, FilterExpr
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Identity / partition
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
MessageRole = Literal["user", "assistant", "system", "tool"]
|
|
27
|
+
MemoryKind = Literal["fact", "episode", "summary", "procedure", "document"]
|
|
28
|
+
MemoryVersionEvent = Literal["created", "updated", "superseded", "invalidated"]
|
|
29
|
+
PackageFormat = Literal["flat", "tiered", "structured"]
|
|
30
|
+
IngestMode = Literal["text", "messages", "verbatim"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Scope(BaseModel):
|
|
34
|
+
"""Identity and partition context for memory operations.
|
|
35
|
+
|
|
36
|
+
Providers declare which fields they require via
|
|
37
|
+
``capabilities().required_scope``.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
model_config = ConfigDict(extra="forbid")
|
|
41
|
+
|
|
42
|
+
user: str | None = None
|
|
43
|
+
agent: str | None = None
|
|
44
|
+
namespace: str | None = None
|
|
45
|
+
thread: str | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class MemoryRef(BaseModel):
|
|
49
|
+
"""Reference to a specific memory within a scope."""
|
|
50
|
+
|
|
51
|
+
model_config = ConfigDict(extra="forbid")
|
|
52
|
+
|
|
53
|
+
id: str
|
|
54
|
+
scope: Scope
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Provenance(BaseModel):
|
|
58
|
+
"""Where a memory came from."""
|
|
59
|
+
|
|
60
|
+
model_config = ConfigDict(extra="forbid")
|
|
61
|
+
|
|
62
|
+
source: str | None = None
|
|
63
|
+
source_url: str | None = None
|
|
64
|
+
source_id: str | None = None
|
|
65
|
+
extractor: str | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Memory(BaseModel):
|
|
69
|
+
"""A single memory unit returned by get/list/search."""
|
|
70
|
+
|
|
71
|
+
model_config = ConfigDict(extra="ignore")
|
|
72
|
+
|
|
73
|
+
id: str
|
|
74
|
+
content: str
|
|
75
|
+
scope: Scope
|
|
76
|
+
kind: MemoryKind | None = None
|
|
77
|
+
created_at: datetime
|
|
78
|
+
updated_at: datetime | None = None
|
|
79
|
+
provenance: Provenance | None = None
|
|
80
|
+
metadata: dict[str, Any] | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# Ingest
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class IngestBase(BaseModel):
|
|
89
|
+
"""Common ingest fields shared by every mode."""
|
|
90
|
+
|
|
91
|
+
model_config = ConfigDict(extra="forbid")
|
|
92
|
+
|
|
93
|
+
scope: Scope
|
|
94
|
+
provenance: Provenance | None = None
|
|
95
|
+
metadata: dict[str, Any] | None = None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TextIngest(IngestBase):
|
|
99
|
+
"""Raw text: conversation transcript, document, note."""
|
|
100
|
+
|
|
101
|
+
mode: Literal["text"] = "text"
|
|
102
|
+
content: str
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Message(BaseModel):
|
|
106
|
+
"""One turn in a structured chat conversation."""
|
|
107
|
+
|
|
108
|
+
model_config = ConfigDict(extra="forbid")
|
|
109
|
+
|
|
110
|
+
role: MessageRole
|
|
111
|
+
content: str
|
|
112
|
+
name: str | None = None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class MessageIngest(IngestBase):
|
|
116
|
+
"""Structured chat messages."""
|
|
117
|
+
|
|
118
|
+
mode: Literal["messages"] = "messages"
|
|
119
|
+
messages: list[Message]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class VerbatimIngest(IngestBase):
|
|
123
|
+
"""Bypass LLM extraction; store the content as a single memory.
|
|
124
|
+
|
|
125
|
+
Capability-gated — only available when
|
|
126
|
+
``capabilities().ingest_modes`` includes ``"verbatim"``.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
mode: Literal["verbatim"] = "verbatim"
|
|
130
|
+
content: str
|
|
131
|
+
kind: MemoryKind | None = None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
IngestInput = Annotated[
|
|
135
|
+
TextIngest | MessageIngest | VerbatimIngest,
|
|
136
|
+
Field(discriminator="mode"),
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class IngestResult(BaseModel):
|
|
141
|
+
"""Per-id outcome of a single ingest call."""
|
|
142
|
+
|
|
143
|
+
model_config = ConfigDict(extra="ignore")
|
|
144
|
+
|
|
145
|
+
created: list[str] = Field(default_factory=list)
|
|
146
|
+
updated: list[str] = Field(default_factory=list)
|
|
147
|
+
unchanged: list[str] = Field(default_factory=list)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# Search
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class SearchRequest(BaseModel):
|
|
156
|
+
"""Provider-agnostic search request."""
|
|
157
|
+
|
|
158
|
+
model_config = ConfigDict(extra="forbid")
|
|
159
|
+
|
|
160
|
+
query: str
|
|
161
|
+
scope: Scope
|
|
162
|
+
limit: int | None = None
|
|
163
|
+
threshold: float | None = None
|
|
164
|
+
filter: FilterExpr | None = None
|
|
165
|
+
reranker: str | None = None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class SearchResult(BaseModel):
|
|
169
|
+
"""A single search hit.
|
|
170
|
+
|
|
171
|
+
``score`` is the backward-compatible composite score (provider-
|
|
172
|
+
specific). Prefer the explicit ``similarity``, ``ranking_score``, and
|
|
173
|
+
``relevance`` fields when the provider exposes them.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
model_config = ConfigDict(extra="ignore")
|
|
177
|
+
|
|
178
|
+
memory: Memory
|
|
179
|
+
score: float
|
|
180
|
+
similarity: float | None = None
|
|
181
|
+
ranking_score: float | None = None
|
|
182
|
+
relevance: float | None = None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class SearchResultPage(BaseModel):
|
|
186
|
+
"""Paginated page of search results."""
|
|
187
|
+
|
|
188
|
+
model_config = ConfigDict(extra="ignore")
|
|
189
|
+
|
|
190
|
+
results: list[SearchResult] = Field(default_factory=list)
|
|
191
|
+
cursor: str | None = None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
# List
|
|
196
|
+
# ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class ListRequest(BaseModel):
|
|
200
|
+
"""Paginated list request scoped to a single user/workspace."""
|
|
201
|
+
|
|
202
|
+
model_config = ConfigDict(extra="forbid")
|
|
203
|
+
|
|
204
|
+
scope: Scope
|
|
205
|
+
limit: int | None = None
|
|
206
|
+
cursor: str | None = None
|
|
207
|
+
filter: FilterExpr | None = None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class ListResultPage(BaseModel):
|
|
211
|
+
"""Paginated page of memories."""
|
|
212
|
+
|
|
213
|
+
model_config = ConfigDict(extra="ignore")
|
|
214
|
+
|
|
215
|
+
memories: list[Memory] = Field(default_factory=list)
|
|
216
|
+
cursor: str | None = None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ---------------------------------------------------------------------------
|
|
220
|
+
# Context packaging
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class PackageRequest(SearchRequest):
|
|
225
|
+
"""Search request augmented with token-budget + format hints."""
|
|
226
|
+
|
|
227
|
+
token_budget: int | None = None
|
|
228
|
+
format: PackageFormat | None = None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class ContextPackage(BaseModel):
|
|
232
|
+
"""Injection-ready context for an AI assistant.
|
|
233
|
+
|
|
234
|
+
Attributes:
|
|
235
|
+
text: Formatted string for prompt injection.
|
|
236
|
+
results: Memories that contributed to ``text`` (debugging / attribution).
|
|
237
|
+
tokens: Estimated context tokens used by ``text``.
|
|
238
|
+
budget_constrained: True iff the requested token budget shaped the
|
|
239
|
+
package — eligible memories were omitted entirely or eligible
|
|
240
|
+
richer detail (L1/L2 tier, query-term-revealing upgrades) was
|
|
241
|
+
suppressed solely because the budget could not afford it.
|
|
242
|
+
Quota-driven demotion (e.g. fixed-cap policy) is NOT flagged.
|
|
243
|
+
Powers the v5 CLI envelope's ``meta.budget_constrained`` field.
|
|
244
|
+
Uses ``StrictBool`` so non-boolean wire values raise instead
|
|
245
|
+
of silently coercing.
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
model_config = ConfigDict(extra="ignore")
|
|
249
|
+
|
|
250
|
+
text: str
|
|
251
|
+
results: list[SearchResult] = Field(default_factory=list)
|
|
252
|
+
tokens: int = 0
|
|
253
|
+
budget_constrained: StrictBool
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# ---------------------------------------------------------------------------
|
|
257
|
+
# Capabilities
|
|
258
|
+
# ---------------------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class CapabilitiesRequiredScope(BaseModel):
|
|
262
|
+
"""Per-operation required-scope manifest.
|
|
263
|
+
|
|
264
|
+
The ``list_`` field is named with a trailing underscore because
|
|
265
|
+
``list`` is a Python builtin used inside the type annotation itself
|
|
266
|
+
(``list[str]``) and shadows it during forward-ref evaluation. The
|
|
267
|
+
JSON wire format still uses ``"list"`` via the alias.
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
|
|
271
|
+
|
|
272
|
+
default: list[str]
|
|
273
|
+
ingest: list[str] | None = None
|
|
274
|
+
search: list[str] | None = None
|
|
275
|
+
get: list[str] | None = None
|
|
276
|
+
delete: list[str] | None = None
|
|
277
|
+
list_: list[str] | None = Field(default=None, alias="list")
|
|
278
|
+
update: list[str] | None = None
|
|
279
|
+
package: list[str] | None = None
|
|
280
|
+
temporal: list[str] | None = None
|
|
281
|
+
graph: list[str] | None = None
|
|
282
|
+
forget: list[str] | None = None
|
|
283
|
+
profile: list[str] | None = None
|
|
284
|
+
reflect: list[str] | None = None
|
|
285
|
+
versioning: list[str] | None = None
|
|
286
|
+
batch: list[str] | None = None
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class CapabilitiesExtensions(BaseModel):
|
|
290
|
+
"""Boolean manifest of supported V3 extensions."""
|
|
291
|
+
|
|
292
|
+
model_config = ConfigDict(extra="forbid")
|
|
293
|
+
|
|
294
|
+
update: bool = False
|
|
295
|
+
package: bool = False
|
|
296
|
+
temporal: bool = False
|
|
297
|
+
graph: bool = False
|
|
298
|
+
forget: bool = False
|
|
299
|
+
profile: bool = False
|
|
300
|
+
reflect: bool = False
|
|
301
|
+
versioning: bool = False
|
|
302
|
+
batch: bool = False
|
|
303
|
+
health: bool = False
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class CustomExtensionMeta(BaseModel):
|
|
307
|
+
model_config = ConfigDict(extra="forbid")
|
|
308
|
+
version: str | None = None
|
|
309
|
+
description: str | None = None
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class Capabilities(BaseModel):
|
|
313
|
+
"""Provider capability surface returned by ``provider.capabilities()``."""
|
|
314
|
+
|
|
315
|
+
model_config = ConfigDict(extra="ignore")
|
|
316
|
+
|
|
317
|
+
ingest_modes: list[IngestMode]
|
|
318
|
+
required_scope: CapabilitiesRequiredScope
|
|
319
|
+
extensions: CapabilitiesExtensions
|
|
320
|
+
custom_extensions: dict[str, CustomExtensionMeta] | None = None
|
|
321
|
+
supported_rerankers: list[str] | None = None
|
|
322
|
+
supported_filter_ops: list[FieldFilterOp] | None = None
|
|
323
|
+
max_token_budget: int | None = None
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# ---------------------------------------------------------------------------
|
|
327
|
+
# Extension-specific types
|
|
328
|
+
# ---------------------------------------------------------------------------
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class GraphSearchRequest(BaseModel):
|
|
332
|
+
model_config = ConfigDict(extra="forbid")
|
|
333
|
+
|
|
334
|
+
query: str
|
|
335
|
+
scope: Scope
|
|
336
|
+
limit: int | None = None
|
|
337
|
+
graph_scope: Literal["nodes", "edges", "episodes"] | None = None
|
|
338
|
+
reranker: str | None = None
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class GraphNode(BaseModel):
|
|
342
|
+
model_config = ConfigDict(extra="ignore")
|
|
343
|
+
id: str
|
|
344
|
+
label: str
|
|
345
|
+
summary: str | None = None
|
|
346
|
+
score: float | None = None
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class GraphEdge(BaseModel):
|
|
350
|
+
model_config = ConfigDict(extra="ignore")
|
|
351
|
+
id: str
|
|
352
|
+
fact: str
|
|
353
|
+
from_: str = Field(alias="from")
|
|
354
|
+
to: str
|
|
355
|
+
valid_at: datetime | None = None
|
|
356
|
+
invalid_at: datetime | None = None
|
|
357
|
+
score: float | None = None
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class GraphResult(BaseModel):
|
|
361
|
+
model_config = ConfigDict(extra="ignore")
|
|
362
|
+
nodes: list[GraphNode] = Field(default_factory=list)
|
|
363
|
+
edges: list[GraphEdge] = Field(default_factory=list)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class Profile(BaseModel):
|
|
367
|
+
model_config = ConfigDict(extra="ignore")
|
|
368
|
+
summary: str
|
|
369
|
+
facts: list[str] | None = None
|
|
370
|
+
updated_at: datetime | None = None
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class Insight(BaseModel):
|
|
374
|
+
model_config = ConfigDict(extra="ignore")
|
|
375
|
+
content: str
|
|
376
|
+
confidence: float
|
|
377
|
+
supporting_memory_ids: list[str] = Field(default_factory=list)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class MemoryVersion(BaseModel):
|
|
381
|
+
model_config = ConfigDict(extra="ignore")
|
|
382
|
+
id: str
|
|
383
|
+
content: str
|
|
384
|
+
created_at: datetime
|
|
385
|
+
parent_id: str | None = None
|
|
386
|
+
event: MemoryVersionEvent
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class HealthStatus(BaseModel):
|
|
390
|
+
"""V3 capability-probe health response.
|
|
391
|
+
|
|
392
|
+
Distinct from AtomicMemory's `/memories/health` runtime snapshot.
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
model_config = ConfigDict(extra="ignore")
|
|
396
|
+
ok: bool
|
|
397
|
+
latency_ms: float | None = None
|
|
398
|
+
version: str | None = None
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""AtomicMemory provider — HTTP client for atomicmemory-core.
|
|
2
|
+
|
|
3
|
+
Port of `atomicmemory-sdk/src/memory/atomicmemory-provider/`. Importing
|
|
4
|
+
this package registers the provider on
|
|
5
|
+
`atomicmemory.memory.registry.default_registry` (and the async registry
|
|
6
|
+
once Phase 4 lands).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from atomicmemory.memory.registry import (
|
|
10
|
+
AsyncProviderRegistration,
|
|
11
|
+
ProviderRegistration,
|
|
12
|
+
default_async_registry,
|
|
13
|
+
default_registry,
|
|
14
|
+
)
|
|
15
|
+
from atomicmemory.providers.atomicmemory.async_provider import AsyncAtomicMemoryProvider
|
|
16
|
+
from atomicmemory.providers.atomicmemory.config import AtomicMemoryProviderConfig
|
|
17
|
+
from atomicmemory.providers.atomicmemory.provider import AtomicMemoryProvider
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _coerce_config(config: object) -> AtomicMemoryProviderConfig:
|
|
21
|
+
if isinstance(config, AtomicMemoryProviderConfig):
|
|
22
|
+
return config
|
|
23
|
+
return AtomicMemoryProviderConfig.model_validate(config)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _factory(config: object) -> ProviderRegistration:
|
|
27
|
+
"""Construct an AtomicMemoryProvider from a config dict or model."""
|
|
28
|
+
return ProviderRegistration(provider=AtomicMemoryProvider(_coerce_config(config)))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _async_factory(config: object) -> AsyncProviderRegistration:
|
|
32
|
+
return AsyncProviderRegistration(provider=AsyncAtomicMemoryProvider(_coerce_config(config)))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
default_registry.register("atomicmemory", _factory)
|
|
36
|
+
default_async_registry.register("atomicmemory", _async_factory)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"AsyncAtomicMemoryProvider",
|
|
41
|
+
"AtomicMemoryProvider",
|
|
42
|
+
"AtomicMemoryProviderConfig",
|
|
43
|
+
]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""AtomicMemoryAgents — agent trust + conflicts category.
|
|
2
|
+
|
|
3
|
+
Port of the agents section of
|
|
4
|
+
`atomicmemory-sdk/src/memory/atomicmemory-provider/handle-impl.ts:1110-1228`.
|
|
5
|
+
|
|
6
|
+
These routes live under ``/agents/*`` (NOT ``/memories/*``).
|
|
7
|
+
``set_trust`` / ``get_trust`` / ``conflicts`` / ``auto_resolve_conflicts``
|
|
8
|
+
are user-scoped; ``resolve_conflict`` is keyed by conflict id directly
|
|
9
|
+
(core resolves without a user context).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
from typing import Any
|
|
16
|
+
from urllib.parse import quote
|
|
17
|
+
|
|
18
|
+
import httpx
|
|
19
|
+
|
|
20
|
+
from atomicmemory.providers.atomicmemory.handle import (
|
|
21
|
+
AutoResolveConflictsResult,
|
|
22
|
+
ConflictResolution,
|
|
23
|
+
ConflictsListResult,
|
|
24
|
+
GetTrustResult,
|
|
25
|
+
ResolveConflictResult,
|
|
26
|
+
SetTrustResult,
|
|
27
|
+
)
|
|
28
|
+
from atomicmemory.providers.atomicmemory.http import HttpOptions, afetch_json, fetch_json
|
|
29
|
+
|
|
30
|
+
Route = Callable[[str], str]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AtomicMemoryAgents:
|
|
34
|
+
"""Agent trust + conflict resolution operations."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, client: httpx.Client, http: HttpOptions, route: Route) -> None:
|
|
37
|
+
self._client = client
|
|
38
|
+
self._http = http
|
|
39
|
+
self._route = route
|
|
40
|
+
|
|
41
|
+
def set_trust(
|
|
42
|
+
self,
|
|
43
|
+
user_id: str,
|
|
44
|
+
agent_id: str,
|
|
45
|
+
trust_level: float,
|
|
46
|
+
display_name: str | None = None,
|
|
47
|
+
) -> SetTrustResult:
|
|
48
|
+
body: dict[str, Any] = {
|
|
49
|
+
"user_id": user_id,
|
|
50
|
+
"agent_id": agent_id,
|
|
51
|
+
"trust_level": trust_level,
|
|
52
|
+
}
|
|
53
|
+
if display_name is not None:
|
|
54
|
+
body["display_name"] = display_name
|
|
55
|
+
raw = fetch_json(
|
|
56
|
+
self._client,
|
|
57
|
+
self._http,
|
|
58
|
+
self._route("/agents/trust"),
|
|
59
|
+
method="PUT",
|
|
60
|
+
json=body,
|
|
61
|
+
)
|
|
62
|
+
return SetTrustResult.model_validate(raw)
|
|
63
|
+
|
|
64
|
+
def get_trust(self, user_id: str, agent_id: str) -> GetTrustResult:
|
|
65
|
+
path = self._route(f"/agents/trust?user_id={quote(user_id, safe='')}&agent_id={quote(agent_id, safe='')}")
|
|
66
|
+
raw = fetch_json(self._client, self._http, path)
|
|
67
|
+
return GetTrustResult.model_validate(raw)
|
|
68
|
+
|
|
69
|
+
def conflicts(self, user_id: str) -> ConflictsListResult:
|
|
70
|
+
path = self._route(f"/agents/conflicts?user_id={quote(user_id, safe='')}")
|
|
71
|
+
raw = fetch_json(self._client, self._http, path)
|
|
72
|
+
return ConflictsListResult.model_validate(raw)
|
|
73
|
+
|
|
74
|
+
def resolve_conflict(self, conflict_id: str, resolution: ConflictResolution) -> ResolveConflictResult:
|
|
75
|
+
path = self._route(f"/agents/conflicts/{quote(conflict_id, safe='')}/resolve")
|
|
76
|
+
raw = fetch_json(
|
|
77
|
+
self._client,
|
|
78
|
+
self._http,
|
|
79
|
+
path,
|
|
80
|
+
method="PUT",
|
|
81
|
+
json={"resolution": resolution},
|
|
82
|
+
)
|
|
83
|
+
return ResolveConflictResult.model_validate(raw)
|
|
84
|
+
|
|
85
|
+
def auto_resolve_conflicts(self, user_id: str) -> AutoResolveConflictsResult:
|
|
86
|
+
raw = fetch_json(
|
|
87
|
+
self._client,
|
|
88
|
+
self._http,
|
|
89
|
+
self._route("/agents/conflicts/auto-resolve"),
|
|
90
|
+
method="POST",
|
|
91
|
+
json={"user_id": user_id},
|
|
92
|
+
)
|
|
93
|
+
return AutoResolveConflictsResult.model_validate(raw)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class AsyncAtomicMemoryAgents:
|
|
97
|
+
"""Async counterpart of :class:`AtomicMemoryAgents`."""
|
|
98
|
+
|
|
99
|
+
def __init__(self, client: httpx.AsyncClient, http: HttpOptions, route: Route) -> None:
|
|
100
|
+
self._client = client
|
|
101
|
+
self._http = http
|
|
102
|
+
self._route = route
|
|
103
|
+
|
|
104
|
+
async def set_trust(
|
|
105
|
+
self,
|
|
106
|
+
user_id: str,
|
|
107
|
+
agent_id: str,
|
|
108
|
+
trust_level: float,
|
|
109
|
+
display_name: str | None = None,
|
|
110
|
+
) -> SetTrustResult:
|
|
111
|
+
body: dict[str, Any] = {
|
|
112
|
+
"user_id": user_id,
|
|
113
|
+
"agent_id": agent_id,
|
|
114
|
+
"trust_level": trust_level,
|
|
115
|
+
}
|
|
116
|
+
if display_name is not None:
|
|
117
|
+
body["display_name"] = display_name
|
|
118
|
+
raw = await afetch_json(
|
|
119
|
+
self._client,
|
|
120
|
+
self._http,
|
|
121
|
+
self._route("/agents/trust"),
|
|
122
|
+
method="PUT",
|
|
123
|
+
json=body,
|
|
124
|
+
)
|
|
125
|
+
return SetTrustResult.model_validate(raw)
|
|
126
|
+
|
|
127
|
+
async def get_trust(self, user_id: str, agent_id: str) -> GetTrustResult:
|
|
128
|
+
path = self._route(f"/agents/trust?user_id={quote(user_id, safe='')}&agent_id={quote(agent_id, safe='')}")
|
|
129
|
+
raw = await afetch_json(self._client, self._http, path)
|
|
130
|
+
return GetTrustResult.model_validate(raw)
|
|
131
|
+
|
|
132
|
+
async def conflicts(self, user_id: str) -> ConflictsListResult:
|
|
133
|
+
path = self._route(f"/agents/conflicts?user_id={quote(user_id, safe='')}")
|
|
134
|
+
raw = await afetch_json(self._client, self._http, path)
|
|
135
|
+
return ConflictsListResult.model_validate(raw)
|
|
136
|
+
|
|
137
|
+
async def resolve_conflict(self, conflict_id: str, resolution: ConflictResolution) -> ResolveConflictResult:
|
|
138
|
+
path = self._route(f"/agents/conflicts/{quote(conflict_id, safe='')}/resolve")
|
|
139
|
+
raw = await afetch_json(
|
|
140
|
+
self._client,
|
|
141
|
+
self._http,
|
|
142
|
+
path,
|
|
143
|
+
method="PUT",
|
|
144
|
+
json={"resolution": resolution},
|
|
145
|
+
)
|
|
146
|
+
return ResolveConflictResult.model_validate(raw)
|
|
147
|
+
|
|
148
|
+
async def auto_resolve_conflicts(self, user_id: str) -> AutoResolveConflictsResult:
|
|
149
|
+
raw = await afetch_json(
|
|
150
|
+
self._client,
|
|
151
|
+
self._http,
|
|
152
|
+
self._route("/agents/conflicts/auto-resolve"),
|
|
153
|
+
method="POST",
|
|
154
|
+
json={"user_id": user_id},
|
|
155
|
+
)
|
|
156
|
+
return AutoResolveConflictsResult.model_validate(raw)
|