memoryo-sdk 0.1.1__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.
@@ -0,0 +1,410 @@
1
+ Metadata-Version: 2.4
2
+ Name: memoryo-sdk
3
+ Version: 0.1.1
4
+ Summary: Python SDK for governed, traceable AI memory with MemoryOS.
5
+ Author-email: MemoryO <support@memoryo.dev>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://memoryo.dev
8
+ Project-URL: Documentation, https://docs.memoryo.dev/sdk/python
9
+ Project-URL: Repository, https://github.com/memengine/memory-api
10
+ Project-URL: Issues, https://github.com/memengine/memory-api/issues
11
+ Keywords: ai,agents,memory,governance,provenance,sdk
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: httpx<1.0,>=0.27
23
+ Requires-Dist: pydantic<3.0,>=2.7
24
+ Dynamic: license-file
25
+
26
+ # MemoryOS Python SDK
27
+
28
+ ```bash
29
+ pip install memoryo-sdk
30
+ ```
31
+
32
+ ## Start simple: solo builders and small teams
33
+
34
+ > **For whom:** solo developers, MVPs, small SaaS apps, and single-agent products.
35
+ > You do **not** need `event_id`, `run_id`, service writers, or authority rules to start. MemoryOS creates safe internal source metadata automatically.
36
+
37
+ ```python
38
+ import os
39
+ from memoryos import Memory
40
+
41
+ mem = Memory(api_key=os.environ["MEMORYOS_API_KEY"])
42
+
43
+ write = mem.add(
44
+ external_user_id="user_123",
45
+ messages=[
46
+ {"role": "user", "content": "I prefer short answers when debugging."},
47
+ {"role": "assistant", "content": "Got it. I will keep debugging replies concise."},
48
+ ],
49
+ )
50
+
51
+ if not write.job_id:
52
+ raise RuntimeError(f"Memory write was not queued: {write.status}")
53
+ job = mem.wait_for_job(write.job_id)
54
+ if not job.succeeded:
55
+ raise RuntimeError(f"Memory write failed: {job.error_summary or job.status}")
56
+
57
+ context = mem.get(
58
+ external_user_id="user_123",
59
+ query="How should I answer this user?",
60
+ )
61
+
62
+ if context.has_context:
63
+ system_prompt = BASE_SYSTEM_PROMPT + "\n\n" + context.system_prompt_addition
64
+ ```
65
+
66
+ This is the recommended first integration. Add source metadata only when more than one backend service writes memories for the same users.
67
+
68
+ ## Multi-service mode: support, billing, CRM, and product services
69
+
70
+ > **For whom:** teams where multiple services can write facts about the same user.
71
+ > Use this when you need auditability, deduplication, conflict handling, or source-of-truth routing.
72
+
73
+ After registering the service writers in the Tenant Dashboard, teams can pass their registered `source.service` keys:
74
+
75
+ ```python
76
+ from memoryos import Memory
77
+
78
+ mem = Memory(api_key=os.environ["MEMORYOS_API_KEY"])
79
+
80
+ mem.add(
81
+ external_user_id="cust_123",
82
+ messages=[{"role": "assistant", "content": "Customer is on the Growth plan."}],
83
+ source=Memory.source(
84
+ "billing-service",
85
+ event_id="invoice_evt_8842", # optional but useful for dedupe/audit
86
+ scope={"workspace_id": "ws_123"},
87
+ evidence=[{"source_type": "billing_record", "reference": "invoice_evt_8842"}],
88
+ ),
89
+ )
90
+
91
+ mem.add(
92
+ external_user_id="cust_123",
93
+ messages=[{"role": "assistant", "content": "Customer support previously saw the Starter plan."}],
94
+ source=Memory.source("support-service"), # event_id and observed_at are generated
95
+ )
96
+ ```
97
+
98
+ Production teams should also bind dedicated API keys to those writers. Then Billing, Support, CRM, and other services can have different authority rules without changing the basic SDK flow.
99
+
100
+ ## New in this release
101
+
102
+ ### Checking if a conversation produced memories
103
+
104
+ ```python
105
+ result = client.add(
106
+ messages=messages,
107
+ external_user_id="user_1",
108
+ )
109
+
110
+
111
+ if result.was_queued:
112
+ print(f"Memory queued: {result.job_id}")
113
+ elif result.nothing_to_extract:
114
+ print("Conversation had no storable information")
115
+ elif result.status == "blocked":
116
+ print(f"Blocked: {result.blocked_reason}")
117
+ ```
118
+
119
+ `was_stored` remains as a deprecated compatibility alias. A queued request is
120
+ not confirmed storage; call `wait_for_job()` and inspect
121
+ `job.created_memory_ids` when you need the exact records created.
122
+ `wait_for_job()` continues polling through the backend's retryable `failed`
123
+ state and returns when the job completes or exhausts retries as `dead`.
124
+
125
+ When retrieval returns `result.clarification`, show its question in your
126
+ existing chat and send the user's answer back with
127
+ `client.answer_clarification(...)`. MemoryOS validates identity, expiry, and
128
+ the conflict transition on the backend.
129
+
130
+ ### Source reviews (0.1.1)
131
+
132
+ When recognized uncertainty has no usable alternative, `result.source_reviews`
133
+ may contain a non-activating review. Present its question and allowed actions in
134
+ your existing chat. Submit the actual user's selection, never a model decision:
135
+
136
+ ```python
137
+ review = result.source_reviews[0] if result.source_reviews else None
138
+ if review:
139
+ # user_action is the user's choice from review.actions in your chat.
140
+ resolution = client.answer_source_review(
141
+ review.id,
142
+ external_user_id="user_1",
143
+ version=review.version,
144
+ action=user_action,
145
+ )
146
+ ```
147
+
148
+ `keep_current` and `dismiss` close the pending interpretation without changing
149
+ stored memory or raising authority. `restate` leaves it pending and returns
150
+ `resolved=False`, `next_step="add_memory"`: obtain the user's actual new statement
151
+ and submit it through normal `add()` ingestion. It is not completed storage.
152
+ The same methods are available with `await` on `AsyncMemory`.
153
+ Reviews are bounded to three recent items, expire seven days after the server's
154
+ last observation, and are absent for historical retrieval. This fallback does
155
+ not fix semantic misclassification or suppress all pending-property context.
156
+
157
+ ### Filtering by time
158
+
159
+ ```python
160
+ recent = client.get(
161
+ query="what has the user been working on",
162
+ external_user_id="user_1",
163
+ time_filter_days=7,
164
+ )
165
+ ```
166
+
167
+ ### Context format options
168
+
169
+ ```python
170
+ import json
171
+
172
+ result = client.get(
173
+ query="user preferences",
174
+ external_user_id="user_1",
175
+ format="json",
176
+ context_max_tokens=300,
177
+ )
178
+
179
+ preferences = json.loads(result.system_prompt_addition)
180
+ ```
181
+
182
+ ### Understanding memory importance trends
183
+
184
+ ```python
185
+ result = client.get(query="technical preferences", external_user_id="user_1")
186
+
187
+ if result.has_context:
188
+ system_prompt = BASE_SYSTEM_PROMPT + result.system_prompt_addition
189
+
190
+ for memory in result.items:
191
+ print(memory.content)
192
+ print(f" trend: {memory.importance_trend}")
193
+ if memory.is_hot:
194
+ print(" [hot tier - served from fast cache]")
195
+ ```
196
+
197
+ ### Reading an EdTech profile
198
+
199
+ If your tenant has the EdTech domain schema enabled, normal `get()` calls already include domain-aware tutoring context in `system_prompt_addition`. Use `get_edtech_profile()` only when your app needs the full structured student profile for UI or analytics.
200
+
201
+ ```python
202
+ profile = client.get_edtech_profile(external_user_id="student_44821")
203
+
204
+ if profile and profile.has_learning_profile:
205
+ print(profile.grade_level)
206
+ print(profile.explanation_style)
207
+ print(profile.weak_topics[:3])
208
+
209
+ if profile and profile.has_exam_context:
210
+ print(profile.exam_name, profile.exam_date)
211
+ ```
212
+
213
+ ## Webhook Signature Verification
214
+
215
+ ```python
216
+ import hashlib
217
+ import hmac
218
+
219
+
220
+ def verify_memoryos_webhook(body_bytes: bytes, signature_header: str, webhook_secret: str) -> bool:
221
+ expected = hmac.new(
222
+ webhook_secret.encode("utf-8"),
223
+ body_bytes,
224
+ hashlib.sha256,
225
+ ).hexdigest()
226
+ return hmac.compare_digest(expected, signature_header)
227
+
228
+
229
+ signature = request.headers["X-MemoryOS-Signature"]
230
+ if not verify_memoryos_webhook(request.body, signature, WEBHOOK_SECRET):
231
+ raise ValueError("Invalid MemoryOS webhook signature")
232
+ ```
233
+
234
+ ```python
235
+ import os
236
+
237
+ from memoryos import Memory
238
+
239
+ client = Memory(api_key=os.environ["MEMORYOS_API_KEY"], base_url="https://api.memoryo.dev")
240
+
241
+ result = client.add(
242
+ external_user_id="student_44821",
243
+ messages=[{"role": "user", "content": "I prefer Python and FastAPI"}],
244
+ )
245
+
246
+ memories = client.get(
247
+ query="programming preferences",
248
+ external_user_id="student_44821",
249
+ limit=3,
250
+ )
251
+
252
+ print(result.status, result.quota_mode)
253
+ print(memories.quota_mode)
254
+ print(memories.items[0].content if memories.items else "No memories found")
255
+ client.close()
256
+ ```
257
+
258
+ `MemoryOS` gives you a typed, low-dependency client for the production API.
259
+
260
+ - Sync client: `Memory`
261
+ - Async client: `AsyncMemory`
262
+ - Runtime dependencies: only `httpx` and `pydantic`
263
+ - Built-in retries on `429` and `5xx`
264
+ - Typed results for add, search, list, delete, and export
265
+
266
+ ## Sync, async, and domain schemas
267
+
268
+ `Memory` and `AsyncMemory` use the same MemoryOS backend.
269
+
270
+ Use `Memory` in normal blocking Python code:
271
+
272
+ ```python
273
+ import os
274
+
275
+ from memoryos import Memory
276
+
277
+ client = Memory(api_key=os.environ["MEMORYOS_API_KEY"])
278
+ result = client.get(query="customer preference", external_user_id="cust_123")
279
+ ```
280
+
281
+ Use `AsyncMemory` in async apps such as FastAPI or async workers:
282
+
283
+ ```python
284
+ import os
285
+
286
+ from memoryos import AsyncMemory
287
+
288
+ client = AsyncMemory(api_key=os.environ["MEMORYOS_API_KEY"])
289
+ result = await client.get(query="customer preference", external_user_id="cust_123")
290
+ ```
291
+
292
+ Async memory is not a different memory engine. It is the same API with `await`.
293
+
294
+ Domain schemas are tenant settings, not separate SDK methods. If your tenant enables EdTech or Customer Support, normal `add()` and `get()` calls automatically use the domain overlay.
295
+
296
+ ```text
297
+ General tenant:
298
+ add() -> general extraction
299
+ get() -> general memory context
300
+
301
+ EdTech tenant:
302
+ add() -> general extraction + EdTech overlay
303
+ get() -> general memory context + tutoring context
304
+
305
+ Support tenant:
306
+ add() -> general extraction + Support overlay
307
+ get() -> general memory context + customer support context
308
+ ```
309
+
310
+ For Support agents, the runtime flow is still:
311
+
312
+ ```python
313
+ memories = client.get(
314
+ query=user_message,
315
+ external_user_id=customer_id,
316
+ agent_id="support-bot",
317
+ )
318
+
319
+ system_prompt = BASE_SUPPORT_PROMPT
320
+ if memories.has_context:
321
+ system_prompt += "\n\n" + memories.system_prompt_addition
322
+ ```
323
+
324
+ MemoryOS remembers customer context. Your own CRM, order, billing, ticket, or banking tools still provide live system truth and actions.
325
+
326
+ ## Retrieval feedback
327
+
328
+ `get()` returns a `retrieval_id`. Keep it with the model response for that turn. If the agent used the memory successfully, was corrected by the user, or had to ask for missing context, send feedback back to MemoryOS.
329
+
330
+ ```python
331
+ result = client.get(
332
+ query=user_message,
333
+ external_user_id="customer_123",
334
+ )
335
+
336
+ # Build your model prompt from result.system_prompt_addition, then answer the user.
337
+
338
+ if result.retrieval_id:
339
+ client.feedback(
340
+ retrieval_id=result.retrieval_id,
341
+ outcome="used_successfully",
342
+ used_memory_ids=[memory.id for memory in result.items],
343
+ agent_confidence=0.9,
344
+ )
345
+ ```
346
+
347
+ When the user corrects the agent or the agent had to ask a clarification, include the correction. MemoryOS records the signal and can queue asynchronous retrospective extraction.
348
+
349
+ ```python
350
+ if result.retrieval_id:
351
+ feedback = client.feedback(
352
+ retrieval_id=result.retrieval_id,
353
+ outcome="user_corrected",
354
+ correction="Actually, the user prefers Hindi replies, not English.",
355
+ )
356
+
357
+ if feedback.queued_retrospective_extraction:
358
+ print("MemoryOS queued a background correction pass")
359
+ ```
360
+ ## Quickstart
361
+
362
+ 1. Create an API key in the MemoryOS dashboard.
363
+ 2. Install the package with `pip install memoryo-sdk`.
364
+ 3. Import `Memory` for sync code or `AsyncMemory` for async apps.
365
+ 4. Pass your API key as `api_key="mem_live_..."`.
366
+ 5. Override `base_url` only for local or self-hosted environments.
367
+ 6. Call `add()` with the tenant's `external_user_id`.
368
+ 7. Check `result.status` and `result.quota_mode` to handle `blocked` or `passthrough` gracefully.
369
+ 8. Call `get()` with the same `external_user_id` to retrieve relevant memories for a query.
370
+ 9. Use `retrieve_result.system_prompt_addition` only when `retrieve_result.quota_mode != "PASSTHROUGH"`.
371
+ 10. Call `list()` to page through stored memories.
372
+ 11. Call `delete()` to archive or hard-delete a memory.
373
+ 12. Call `export()` to download the user export bundle.
374
+ 13. All calls raise typed SDK errors on failure.
375
+ 14. `401` maps to `AuthError`.
376
+ 15. `404` maps to `NotFoundError`.
377
+ 16. `429` maps to `RateLimitError`.
378
+ 17. Use the `examples/` folder for common integration patterns.
379
+ 18. If a domain schema is enabled for the tenant, keep using the same `add()` and `get()` calls.
380
+
381
+ ## Handling Degraded Responses
382
+
383
+ ```python
384
+ result = client.get(query="...", external_user_id="...")
385
+
386
+ if result.is_passthrough:
387
+ # MemoryOS quota exhausted or system degraded
388
+ # Your AI still works - just without memory context
389
+ system_prompt = BASE_SYSTEM_PROMPT
390
+ elif result.is_degraded:
391
+ # Serving from cache - fewer memories than usual
392
+ # Still worth using
393
+ system_prompt = BASE_SYSTEM_PROMPT + result.system_prompt_addition
394
+ else:
395
+ # Full memory context
396
+ system_prompt = BASE_SYSTEM_PROMPT + result.system_prompt_addition
397
+
398
+ # Always call your LLM - never skip because of MemoryOS state
399
+ response = llm.complete(system_prompt, user_message)
400
+ ```
401
+
402
+ ```python
403
+ result = client.add(messages=messages, external_user_id="...")
404
+
405
+ if result.processing_status == "delayed":
406
+ # Queue is busy - memory will be stored but takes longer
407
+ # ETA in seconds:
408
+ eta = result.processing_eta_seconds
409
+ # Log it - do not surface to end user
410
+ ```
@@ -0,0 +1,11 @@
1
+ memoryo_sdk-0.1.1.dist-info/licenses/LICENSE,sha256=kkdaH3wKAANc1uJ7pdmP0PyoxrertgF9rdFVZMRIJyY,1064
2
+ memoryos/__init__.py,sha256=bz59uMxrQGHuy_5BQaHl1YxPlKhVRwGH2pGqDAzMQkE,506
3
+ memoryos/async_client.py,sha256=VEjySGtkESASlR3Oxx6-15RaZVfzBIUI2Q2QNBYJBQ0,17791
4
+ memoryos/client.py,sha256=eWokGNlc4qemVKREGnqQUNwqy7aINKtP5CZkDeejVQs,17558
5
+ memoryos/errors.py,sha256=7bxdHDdMUM6RbHy8UF0wOhT_NUOrZ3oTsw4YAtD2xHc,1233
6
+ memoryos/types.py,sha256=A99qwG18cqWp7mcHWdBMHKCPAFdwrRVfttbXNKIKRxk,16007
7
+ memoryos/universal.py,sha256=Bep8ZelFf0kgU7rQyc-BzhEyeJTL8Pu7waedznRajpc,9451
8
+ memoryo_sdk-0.1.1.dist-info/METADATA,sha256=L7xwzBIkFsMVtBXdJRH7oGUSfmADkP3wht8rDak1FGk,13921
9
+ memoryo_sdk-0.1.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
10
+ memoryo_sdk-0.1.1.dist-info/top_level.txt,sha256=DQsBYhKPVDcTbN7KnuuKpLGlaAqe4BIMCe1n1fs3d_M,9
11
+ memoryo_sdk-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MemoryO
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ memoryos
memoryos/__init__.py ADDED
@@ -0,0 +1,18 @@
1
+ from memoryos.async_client import AsyncMemory
2
+ from memoryos.client import Memory
3
+ from memoryos.errors import AuthError, MemoryOSError, NotFoundError, RateLimitError
4
+ from memoryos.types import EvidenceReference, MemorySource, ProposedMemory
5
+ from memoryos.universal import UniversalMemory
6
+
7
+ __all__ = [
8
+ "AsyncMemory",
9
+ "AuthError",
10
+ "EvidenceReference",
11
+ "Memory",
12
+ "MemoryOSError",
13
+ "MemorySource",
14
+ "NotFoundError",
15
+ "ProposedMemory",
16
+ "RateLimitError",
17
+ "UniversalMemory",
18
+ ]