Python SDK
Use synchronous or asynchronous Python clients with FishMem Cloud and self-hosted FishMem.
Install
pip install fishmemThe distribution and import package are both fishmem.
Python 3.10 or newer is required.
Synchronous client
from fishmem import FishMem
with FishMem(
api_key="fm_...",
base_url="https://fishmem.com",
) as fishmem:
added = fishmem.memories.add_and_wait(
{
"content": "Alex prefers concise answers.",
"user_id": "alex",
},
idempotency_key="alex-answer-style-v1",
)
result = fishmem.memories.search(
{
"query": "How should I answer Alex?",
"user_id": "alex",
"top_k": 5,
"filters": {
"not": {
"field": "content",
"operator": "icontains",
"value": "outdated",
}
},
}
)Python passes the same validated logical filter grammar as HTTP and the
TypeScript SDK. Keep user_id, agent_id, and run_id at the top level; they
cannot be placed inside an OR expression. See Memory filters.
add_async(...) returns the durable event_id immediately. Despite its name,
the method is also available on the synchronous client; it describes server
processing, not Python coroutine behavior:
receipt = fishmem.memories.add_async(
{"content": "Alex prefers concise answers.", "user_id": "alex"},
idempotency_key="alex-answer-style-v2",
)
event = fishmem.events.wait(receipt["event_id"])Use memories.add({...,"infer": False}) for synchronous verbatim records.
For self-hosting, set base_url to the deployment origin without /v1.
Asynchronous client
from fishmem import AsyncFishMem
async with AsyncFishMem(api_key="fm_...") as fishmem:
result = await fishmem.memories.search(
{"query": "answer style", "user_id": "alex"}
)AsyncFishMem exposes the same resources and method names as FishMem:
health.get;documents: ingest, upload, create_upload, get_upload, complete_upload, delete_upload, search, list, list_all, get, content, and delete;entities: list, list_all, get, and delete structural user, agent, and run scopes;memories: add, search, list, list_all, get, update, delete, delete_all, add_async, add_and_wait, batch_update, batch_delete, history, get_feedback, set_feedback, and clear_feedback;events: list, get, and wait;state: get and history;beliefs: get;profile: get;operations: list, get, retry, and wait;exports.createandimports.create.
Read the opt-in governed belief projection with the same query contract as the REST API:
view = fishmem.beliefs.get(
{
"user_id": "alex",
"subject": "Alex",
"attribute": "answer_style",
"view": "audit",
}
)The async client exposes await fishmem.beliefs.get(...). A disabled
projection is returned as data; it is not treated as a transport error.
Pagination
for memory in fishmem.memories.list_all(
{"user_id": "alex"}, limit=100
):
process(memory)For AsyncFishMem, use async for.
Scope entities use the same pagination pattern:
for entity in fishmem.entities.list_all(entity_type="user", limit=100):
print(entity["id"], entity["total_memories"])entities.delete(type, id, idempotency_key="...") removes every active
memory in that structural scope from recall and preserves deletion history.
Batch mutations are durable and require a retry identity:
queued = fishmem.memories.batch_update(
[
{"memory_id": "mem_1", "content": "Updated record"},
{"memory_id": "mem_2", "metadata": {"source": "crm"}},
],
idempotency_key="crm-refresh-v1",
)
completed = fishmem.operations.wait(queued["id"])batch_delete([{"memory_id": "..."}], idempotency_key="...") uses the same
operation result contract. The async client exposes awaitable versions, and
FishMemDesktop routes both methods through one local CLI invocation.
Feedback uses the same sync, async, and Desktop method names:
fishmem.memories.set_feedback(
"mem_1",
{
"rating": "negative",
"reason": "This record is out of date",
"request_id": "req_123",
},
idempotency_key="req-123-feedback-v1",
)
current = fishmem.memories.get_feedback("mem_1")Use clear_feedback(..., idempotency_key="...") to clear the current signal
without removing its audit history.
Document pagination uses the same pattern:
for document in fishmem.documents.list_all(
{"user_id": "alex"}, limit=100
):
process(document)Binary and textual files use the asynchronous source-asset lifecycle in both sync and async HTTP clients:
from pathlib import Path
queued = fishmem.documents.upload(
Path("./handbook.pdf"),
{"source_key": "docs/handbook.pdf", "user_id": "alex"},
idempotency_key="handbook-pdf-v1",
)
ready = fishmem.operations.wait(
queued["operation"]["id"],
interval=1.0,
timeout=600.0,
)The client calculates SHA-256, creates an immutable source asset, uploads the exact bytes, and queues extraction. It returns the queued operation rather than waiting for Docling. Supported PDF, Office, EPUB, email, image, and text files may be up to 25,000,000 bytes and 300 extracted pages. Audio and video are not accepted.
FishMemDesktop.documents.upload() remains local and text-only: it validates
at most 1,000,000 bytes of UTF-8 and never calls Docling or a remote embedding
provider.
For manual lifecycle control, both clients expose create_upload,
get_upload, complete_upload, and delete_upload. Failed or dead extraction
can be redriven with operations.retry(operation_id). See
Asynchronous file extraction.
Errors
from fishmem import FishMemError
try:
fishmem.memories.get("missing")
except FishMemError as error:
print(error.status, error.code, error.request_id)The client uses httpx, accepts a custom transport for testing, and applies no
hidden automatic retries.