cortadel
Official Python SDK for Cortadel — self-hosted long-term temporal graph memory for AI agents. A thin, typed client over the Cortadel REST API.
pip install cortadel
# or: uv add cortadel / poetry add cortadel
Python ≥ 3.10. Ships two facades: an async client and a real blocking client — pick whichever fits your program.
Quickstart (async)
import asyncio
from cortadel import ChatMessage, CortadelClient, SearchOptions
async def main() -> None:
async with CortadelClient("http://localhost:3001", "alice") as cortadel:
# apiKey: pass api_key="<token>" — omit when the server runs with auth disabled
# Store
await cortadel.add("Alice prefers dark mode and ships on Fridays.")
# Recall (hybrid BM25 + vector + RRF)
hits = await cortadel.search("what are alice's preferences?", SearchOptions(top_k=5))
for h in hits.results:
print(h.rrf_score, h.content)
# Ingest a conversation
await cortadel.add_conversation([
ChatMessage(role="user", content="I'm allergic to peanuts."),
ChatMessage(role="assistant", content="Noted — I'll avoid peanut recipes."),
])
# List / get / delete
page = await cortadel.list()
one = await cortadel.get(page.items[0].id)
await cortadel.delete([page.items[0].id])
asyncio.run(main())
Quickstart (blocking)
from cortadel import SyncCortadelClient
with SyncCortadelClient("http://localhost:3001", "alice") as cortadel:
cortadel.add("Alice prefers dark mode and ships on Fridays.")
hits = cortadel.search("what are alice's preferences?")
for h in hits.results:
print(h.rrf_score, h.content)
SyncCortadelClient is a real blocking client, not asyncio.run(...) called once per method —
see cortadel/sync_client.py's module docstring for why that distinction matters (connection-pool
reuse, and correctness when called from a thread that already has a running event loop) and how it
is implemented (one persistent background event loop, for the client's lifetime).
Auth
Pass api_key in either constructor and every request carries Authorization: Bearer <key>. Omit
it (or leave it None) when the server runs with auth disabled — no header is sent, and the
client never mutates an httpx.AsyncClient you bring in either way.
import os
from cortadel import CortadelClient
cortadel = CortadelClient("https://my-box:3001", "alice", api_key=os.environ.get("CORTADEL_API_KEY"))
Reuse a single client per base URL + user. Every call it makes is scoped to the user_id you
construct it with.
Methods
Both clients expose the same seven methods (async def on CortadelClient, blocking on
SyncCortadelClient):
| Method | Returns | Notes |
|---|---|---|
add(text, options=None) |
MemoryCreated |
Store a memory. options.infer (default True) runs background entity/category extraction; False stores verbatim (dedup still applies). |
add_conversation(messages, options=None) |
ConversationResult |
Distill atomic facts from a transcript and store each one. |
search(query, options=None) |
SearchResults |
Hybrid search (BM25 + vector fused with RRF); set options.rerank = "cross_encoder" to rerank. |
list(options=None) |
MemoryList |
Paginated, newest-first. options.size defaults to 20 (a deliberate SDK-wide choice — see below). |
get(memory_id) |
MemoryDetail | None |
None when the memory doesn't exist; never raises for a 404. Content field is .text. |
delete(memory_ids) |
str |
Deletes one or more memories; returns the server's confirmation message. |
health() |
HealthResult |
Database + embedding provider reachability. Does not raise when the server reports itself degraded — a degraded server is a normal return value (status == "degraded"), not an exception. |
ListOptions.size defaults to 20, not the REST contract's own default of 10 — kept in sync
with the .NET and TypeScript SDKs so every Cortadel SDK behaves identically regardless of which
one you're reading examples for.
Errors
Any non-success response — other than a degraded health check, which health() returns instead
of raising — raises a CortadelError:
from cortadel import CortadelClient, CortadelError
async with CortadelClient("http://localhost:3001", "alice") as cortadel:
try:
await cortadel.add("")
except CortadelError as err:
print(err.status, err.code, err.message)
# asyncio.CancelledError from a cancelled task/timeout propagates untouched instead —
# it is never wrapped as a CortadelError.
| Attribute | Meaning |
|---|---|
status |
HTTP status code. 0 when the transport failed before a status was known. |
code |
Machine-readable error code (e.g. not_found, validation_error). |
message |
Human-readable message — for a 400 from model validation, this folds in the |
| server's per-field errors instead of a generic "the request failed". |
Bring your own HTTP client
Both constructors accept http_client: httpx.AsyncClient to reuse an existing client (connection
pooling, proxies, custom TLS, etc. all carry over). It is never mutated or closed by the SDK —
you own its lifecycle either way.
import httpx
from cortadel import CortadelClient
async with httpx.AsyncClient(proxy="http://proxy.internal:8080") as http_client:
cortadel = CortadelClient("http://localhost:3001", "alice", http_client=http_client)
...
License
Apache-2.0
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cortadel-1.0.0.tar.gz.
File metadata
- Download URL: cortadel-1.0.0.tar.gz
- Upload date:
- Size: 31.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ab67299d91488e39d04d0dfa422488dfbb633082ebec9ed0447bd8fa772a07c
|
|
| MD5 |
608c19bec5cfb06cbe820fb3af033503
|
|
| BLAKE2b-256 |
e0dd89ce7d54e80f7d07b991e364aef3aff4420a6deb501d9a5efe97b27379bf
|
File details
Details for the file cortadel-1.0.0-py3-none-any.whl.
File metadata
- Download URL: cortadel-1.0.0-py3-none-any.whl
- Upload date:
- Size: 61.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55e9a3f9e2c437643457c04775efa95d7bb8920053523727be6958af3b853dd8
|
|
| MD5 |
e187e1710f0779f211dab2b1a5f5c42f
|
|
| BLAKE2b-256 |
d792c58c556fe07e5d474e03749d4cda502c9f59d759b28fdd91b096ff50521d
|