UniContext
Universal Python SDK for agent memory and context with a unified API across multiple providers.
UniContext is HTTP-first: built-in providers call provider REST APIs via httpx. Switch providers by changing the provider name and API key.
Features
- Unified, provider-agnostic client API
- Typed models and input validation
- Consistent error types across providers
- Shared HTTP transport (connection pooling)
Installation
From PyPI:
pip install unicontext
From source (development):
pip install -e .
Optional development tools:
pip install -e ".[dev]"
Quick start
This example uses Letta (archives mode) because it has synchronous IDs you can get() and delete() immediately.
from unicontext import MemoryInput, Scope, SearchQuery, create_client
client = create_client(provider="letta", api_key="YOUR_LETTA_API_KEY")
scope = Scope(user_id="user_123")
created = client.add(MemoryInput(text="User prefers Python for backend"), scope)
hits = client.search(SearchQuery(query="backend", scope=scope, limit=5))
for hit in hits:
print(hit.record.content)
fetched = client.get(memory_id=created.id, scope=scope)
print(fetched.content)
client.delete(memory_id=created.id, scope=scope)
Switching providers
from unicontext import create_client
mem0 = create_client(provider="mem0", api_key="YOUR_MEM0_API_KEY")
supermemory = create_client(provider="supermemory", api_key="YOUR_SUPERMEMORY_API_KEY")
zep = create_client(provider="zep", api_key="YOUR_ZEP_API_KEY")
letta = create_client(provider="letta", api_key="YOUR_LETTA_API_KEY")
Core concepts
Scope
Scope selects where the memory lives (user, agent, thread, tags).
from unicontext import Scope
Scope(user_id="user_123")
Scope(user_id="user_123", thread_id="thread_456")
Scope(user_id="user_123", tags=["preferences", "onboarding"])
Inputs
MemoryInput must provide exactly one of text or messages.
from unicontext import MemoryInput
MemoryInput(text="User prefers dark mode")
MemoryInput(messages=[{"role": "user", "content": "Hi"}])
Provider notes
UniContext normalizes the public API, but providers have different semantics.
Mem0
add()is asynchronous; the returned ID may be an event ID.- Use
search()after processing completes to retrieve the finalized memory ID.
import time
from unicontext import MemoryInput, Scope, SearchQuery, create_client
client = create_client(provider="mem0", api_key="YOUR_MEM0_API_KEY")
scope = Scope(user_id="user_123")
client.add(MemoryInput(text="User likes espresso"), scope)
time.sleep(20)
hits = client.search(SearchQuery(query="espresso", scope=scope, limit=5))
if hits:
memory_id = hits[0].record.id
client.delete(memory_id=memory_id, scope=scope)
Supermemory
- Scope is primarily tag-driven.
delete_by_scope()requires at least one tag to reduce accidental deletes.
Zep
- Dual routing: if
scope.thread_idis set,add()writes to a thread; otherwise it writes to the knowledge graph. search()targets the knowledge graph.get()anddelete()are not supported for graph records.
Letta
- Default mode uses archives and passages.
- If you do not provide an archive ID, UniContext auto-creates and caches one per
scope.user_id.
Error handling
from unicontext import Scope, SearchQuery, create_client
from unicontext.exceptions import AuthError, ProviderError, ValidationError
client = create_client(provider="letta", api_key="YOUR_LETTA_API_KEY")
scope = Scope(user_id="user_123")
try:
client.search(SearchQuery(query="hello", scope=scope, limit=5))
except ValidationError as e:
print(e.message)
except AuthError as e:
print(e.message)
except ProviderError as e:
print(e.message)
Development
Run unit tests:
python -m pytest -v
Run the pre-release checks:
python scripts/pre_release.py
Metadata
Release files for unicontext 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| unicontext-0.1.0.tar.gz | 20.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| unicontext-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 46.8 kB
Release files / unicontext-0.1.0.tar.gz
| Download URL | unicontext-0.1.0.tar.gz |
|---|---|
| Size | 20.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
64b18166d6753b7c1e6b33890f9905c79a1d01cd4c6fff08a5c09add31d8612e
|
|
BLAKE2b-256 checksum How to use checksums |
9c1d3a6bfeabe84b982e8d42c763a38e516fcc60342be888fcfb35eb55e9bf68
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.7
|
Release files / unicontext-0.1.0-py3-none-any.whl
| Download URL | unicontext-0.1.0-py3-none-any.whl |
|---|---|
| Size | 26.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
51520a684af3910446efd9eaa1949440f3518a669061c378cd5e690bdb296e80
|
|
BLAKE2b-256 checksum How to use checksums |
5541cccd73cf3bc12ae74670252f7df1e86791a720cdb5c423377d136f6e0948
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.7
|