MemorySyncMemorySync
Python SDK

Async Usage

Anywhere you would reach for asyncio — async web handlers, background workers, anything fan-out or fan-in — use AsyncMemorySyncClient. It is method-for-method identical to the sync client, with one rule: clean up with aclose(), not close().

When the async client pays off

  • You\u2019re inside an async web framework and don\u2019t want a thread per request.
  • You need to fan out many add or query calls in parallel.
  • You\u2019re wiring an LLM agent loop and the rest of the loop is already awaitables.
  • You want to bound concurrency with asyncio.Semaphore instead of thread pools.

A minimal async program

PYTHON
import os, asyncio
from memorysync import AsyncMemorySyncClient
async def main():
client = AsyncMemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
try:
await client.add(text="started new feature work")
hits = await client.query("recent work", k=3)
for m in hits.memories:
print(m.id, m.text)
finally:
await client.aclose()
asyncio.run(main())

Sync vs async cheatsheet

ConcernMemorySyncClientAsyncMemorySyncClient
Importfrom memorysync import MemorySyncClientfrom memorysync import AsyncMemorySyncClient
Method callclient.add(...)await client.add(...)
Cleanupclient.close()await client.aclose()
Use inside an async web frameworkNoYes
Use inside a scriptYesYes (wrap in asyncio.run)

Fanning calls out with gather()

Multiple recalls in parallel is the canonical async use-case. Always bound concurrency — even though the server scales, your own process should not open hundreds of sockets at once.

PYTHON
import asyncio
from memorysync import AsyncMemorySyncClient
async def fanout(client, queries):
sem = asyncio.Semaphore(8)
async def run(q):
async with sem:
return await client.query(q, k=3)
return await asyncio.gather(*(run(q) for q in queries))
async def main():
client = AsyncMemorySyncClient(api_key=..., base_url=...)
try:
results = await fanout(client, ["q1", "q2", "q3", "q4"])
for r in results:
print(len(r.memories))
finally:
await client.aclose()
asyncio.run(main())

Cancellation and timeouts

The async client honours cancellation. If the calling task is cancelled, the in-flight HTTP request is aborted promptly. Set the per-call ceiling with asyncio.wait_for:

PYTHON
async def quick_recall(client, q):
return await asyncio.wait_for(client.query(q, k=3), timeout=2.0)
Constructor timeout still applies
timeout on the constructor is the upper bound for any single HTTP call. wait_for is the upper bound for the whole awaited operation including retries you may layer on top.

Reusing one client across an async server

Build the client once at startup, reuse it across every request, and close it at shutdown. Do not build a client per request — you would pay TLS handshake costs every call.

PYTHON
# Generic startup/shutdown wiring for any async web framework.
from contextlib import asynccontextmanager
from memorysync import AsyncMemorySyncClient
mem: AsyncMemorySyncClient | None = None
@asynccontextmanager
async def lifespan():
global mem
mem = AsyncMemorySyncClient(api_key=..., base_url=...)
try:
yield
finally:
await mem.aclose()
# Inside any async handler:
async def note_handler(text: str):
assert mem is not None
return await mem.add(text=text)

Async-specific pitfalls

  • Forgetting await on a method call — you get a coroutine object back, not a result.
  • Calling close() on the async client. The method does not exist; use aclose().
  • Sharing one async client across event loops. Each loop should own its own.
  • Spawning unbounded asyncio.create_task calls without a semaphore. That bypasses your concurrency budget.