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.
Files changed (67) hide show
  1. atomicmemory/__init__.py +166 -0
  2. atomicmemory/_version.py +3 -0
  3. atomicmemory/client/__init__.py +22 -0
  4. atomicmemory/client/async_memory_client.py +202 -0
  5. atomicmemory/client/atomic_memory_client.py +181 -0
  6. atomicmemory/client/memory_client.py +292 -0
  7. atomicmemory/core/__init__.py +34 -0
  8. atomicmemory/core/errors.py +122 -0
  9. atomicmemory/core/events.py +65 -0
  10. atomicmemory/core/logging.py +37 -0
  11. atomicmemory/core/retry.py +124 -0
  12. atomicmemory/core/validation.py +22 -0
  13. atomicmemory/embeddings/__init__.py +16 -0
  14. atomicmemory/embeddings/base.py +39 -0
  15. atomicmemory/embeddings/sentence_transformers.py +104 -0
  16. atomicmemory/kv_cache/__init__.py +17 -0
  17. atomicmemory/kv_cache/adapter.py +50 -0
  18. atomicmemory/kv_cache/memory_storage.py +98 -0
  19. atomicmemory/kv_cache/sqlite_storage.py +122 -0
  20. atomicmemory/memory/__init__.py +82 -0
  21. atomicmemory/memory/filters.py +68 -0
  22. atomicmemory/memory/pipeline.py +42 -0
  23. atomicmemory/memory/provider.py +397 -0
  24. atomicmemory/memory/registry.py +95 -0
  25. atomicmemory/memory/service.py +199 -0
  26. atomicmemory/memory/types.py +398 -0
  27. atomicmemory/providers/__init__.py +5 -0
  28. atomicmemory/providers/atomicmemory/__init__.py +43 -0
  29. atomicmemory/providers/atomicmemory/agents.py +156 -0
  30. atomicmemory/providers/atomicmemory/async_handle_impl.py +198 -0
  31. atomicmemory/providers/atomicmemory/async_provider.py +245 -0
  32. atomicmemory/providers/atomicmemory/audit.py +74 -0
  33. atomicmemory/providers/atomicmemory/config.py +38 -0
  34. atomicmemory/providers/atomicmemory/config_handle.py +123 -0
  35. atomicmemory/providers/atomicmemory/handle.py +513 -0
  36. atomicmemory/providers/atomicmemory/handle_impl.py +325 -0
  37. atomicmemory/providers/atomicmemory/http.py +255 -0
  38. atomicmemory/providers/atomicmemory/lessons.py +133 -0
  39. atomicmemory/providers/atomicmemory/lifecycle.py +202 -0
  40. atomicmemory/providers/atomicmemory/mappers.py +125 -0
  41. atomicmemory/providers/atomicmemory/path.py +20 -0
  42. atomicmemory/providers/atomicmemory/provider.py +300 -0
  43. atomicmemory/providers/atomicmemory/scope_mapper.py +98 -0
  44. atomicmemory/providers/mem0/__init__.py +41 -0
  45. atomicmemory/providers/mem0/async_provider.py +191 -0
  46. atomicmemory/providers/mem0/config.py +51 -0
  47. atomicmemory/providers/mem0/http.py +195 -0
  48. atomicmemory/providers/mem0/mappers.py +145 -0
  49. atomicmemory/providers/mem0/provider.py +202 -0
  50. atomicmemory/py.typed +0 -0
  51. atomicmemory/search/__init__.py +47 -0
  52. atomicmemory/search/chunking.py +161 -0
  53. atomicmemory/search/ranking.py +94 -0
  54. atomicmemory/search/semantic_search.py +130 -0
  55. atomicmemory/search/similarity.py +110 -0
  56. atomicmemory/storage/__init__.py +63 -0
  57. atomicmemory/storage/_mapping.py +305 -0
  58. atomicmemory/storage/async_client.py +208 -0
  59. atomicmemory/storage/client.py +339 -0
  60. atomicmemory/storage/errors.py +115 -0
  61. atomicmemory/storage/types.py +305 -0
  62. atomicmemory/utils/__init__.py +5 -0
  63. atomicmemory/utils/environment.py +23 -0
  64. atomicmemory-1.0.0.dist-info/METADATA +146 -0
  65. atomicmemory-1.0.0.dist-info/RECORD +67 -0
  66. atomicmemory-1.0.0.dist-info/WHEEL +4 -0
  67. 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,5 @@
1
+ """Concrete memory providers.
2
+
3
+ Each subpackage (`atomicmemory.providers.atomicmemory`, `mem0`) registers
4
+ itself with the default registries on import.
5
+ """
@@ -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)