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
addorquerycalls 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.Semaphoreinstead of thread pools.
A minimal async program
import os, asynciofrom memorysync import AsyncMemorySyncClientasync 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
| Concern | MemorySyncClient | AsyncMemorySyncClient |
|---|---|---|
| Import | from memorysync import MemorySyncClient | from memorysync import AsyncMemorySyncClient |
| Method call | client.add(...) | await client.add(...) |
| Cleanup | client.close() | await client.aclose() |
| Use inside an async web framework | No | Yes |
| Use inside a script | Yes | Yes (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.
import asynciofrom memorysync import AsyncMemorySyncClientasync 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:
async def quick_recall(client, q):return await asyncio.wait_for(client.query(q, k=3), timeout=2.0)
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.
# Generic startup/shutdown wiring for any async web framework.from contextlib import asynccontextmanagerfrom memorysync import AsyncMemorySyncClientmem: AsyncMemorySyncClient | None = None@asynccontextmanagerasync def lifespan():global memmem = AsyncMemorySyncClient(api_key=..., base_url=...)try:yieldfinally:await mem.aclose()# Inside any async handler:async def note_handler(text: str):assert mem is not Nonereturn await mem.add(text=text)
Async-specific pitfalls
- Forgetting
awaiton a method call — you get a coroutine object back, not a result. - Calling
close()on the async client. The method does not exist; useaclose(). - Sharing one async client across event loops. Each loop should own its own.
- Spawning unbounded
asyncio.create_taskcalls without a semaphore. That bypasses your concurrency budget.