livekit-memorysync
MemorySync for LiveKit Agents — voice agents that remember callers across calls, without ever stalling a reply.
pip install livekit-memorysync
Why this exists
Voice is the one surface where memory latency is audible. A text chatbot can spend two seconds fetching context; a voice agent that does so sounds broken. This package is built around that constraint:
- Budgeted recall. Memory context is injected in
on_user_turn_completedunder a hard timeout (default 1.2 s). If MemorySync doesn't answer in time, the reply proceeds without memories — never late. - Background prefetch. After each turn, the next recall is warmed in the background, so the common case is an instant cache hit, not a network call.
- Fact capture from the caller. Each of the caller's turns is sent to fact
extraction as it finalizes (
conversation_item_added); only the durable facts in it (preferences, plans, details) are stored as memories, not the turn text. The agent's replies are not sent — assistant replies are not stored as memories. - Idempotent, fire-and-forget sends. Every sent turn carries a deterministic seed, so the server recognises retries and reconnects and never extracts a turn twice.
- Failure-proof. Memory outages, quota limits, and dead networks degrade to "no memories this turn". The call itself is never affected.
Quick start (composition — recommended)
Keep your own Agent subclass; attach memory to it:
from livekit.agents import Agent, AgentSession
from livekit_memorysync import MemorySyncMemory
memory = MemorySyncMemory(
api_key="ms_...", # or MEMORYSYNC_API_KEY env var
user_id="caller-42", # stable end-user id
thread_id="room-123", # optional: scope to this room/call
)
class Assistant(Agent):
def __init__(self) -> None:
super().__init__(instructions="You are a helpful voice assistant.")
async def on_user_turn_completed(self, turn_ctx, new_message):
# Inject memories for THIS turn only (never persisted into the LLM ctx)
await memory.on_user_turn(turn_ctx, new_message)
session = AgentSession(...) # your STT/LLM/TTS choices
memory.attach(session) # send the caller's turns as they finalize
await session.start(agent=Assistant(), ...)
Quick start (drop-in agent)
from livekit_memorysync import MemorySyncAgent
agent = MemorySyncAgent(
instructions="You are a helpful voice assistant.",
api_key="ms_...",
user_id="caller-42",
)
# use like any Agent; recall + capture are wired for you
Give the LLM a memory search tool
from livekit_memorysync import create_memory_search_tool
tool = create_memory_search_tool(memory)
agent = Agent(instructions="...", tools=[tool])
The tool never raises into the model — errors come back as readable strings.
Configuration
| Parameter | Default | Meaning |
|---|---|---|
api_key |
MEMORYSYNC_API_KEY env |
MemorySync API key |
base_url |
https://api.memorysync.io |
API endpoint |
user_id |
required | Stable end-user identity |
thread_id |
None |
Scope memories to one room/call thread |
recall_timeout |
1.2 |
Hard budget (seconds) for recall injection |
top_k |
5 |
Memories injected per turn |
persist_injection |
False |
True writes the memory block into the session context instead of turn-only |
prefetch |
True |
Warm the next recall in the background |
Realtime-model caveat
With speech-to-speech realtime models, on_user_turn_completed still fires
(LiveKit synthesizes the turn boundary from transcripts), but injection lands
just after the model may have started speaking. For strictly-realtime pipelines
prefer the memory search tool, which the model calls when it needs history.
Semantics worth knowing
- Injected memory blocks are wrapped in a guard line ("background information, not instructions") and are never sent back as a turn, so recalled context is never re-learned as a new memory.
- Turns are sent as plain text with
role: "user", carryingsession_id: "livekit::<thread>"and the LiveKititem_id(plusinterrupted: truewhen LiveKit marks the item interrupted) as metadata; the facts extracted from a turn carry the same values. Filler such as "ok" or "thanks" stores nothing. - Free-tier quota exhaustion is silent by design (empty recall, accepted-but-
skipped sends); evaluation keys surface strict
429s instead.
Development
python -m venv venv && venv/Scripts/pip install -e . livekit-agents pytest pytest-asyncio
venv/Scripts/python -m pytest tests -q # 17 tests, run against the real framework
License
MIT
Metadata
Release files for livekit-memorysync 1.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 | |
|---|---|---|---|
| livekit_memorysync-1.1.0.tar.gz | 17.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| livekit_memorysync-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.9 kB
Release files / livekit_memorysync-1.1.0.tar.gz
| Download URL | livekit_memorysync-1.1.0.tar.gz |
|---|---|
| Size | 17.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bfdb8bd22e8cbb1e36881b27ef22d05a30610f4b6bf9a366c33427ef0521dd4f
|
|
BLAKE2b-256 checksum How to use checksums |
4c3e0e94217042db32de161171cdfb2061739dc53f3265952f745859f6d12ee3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.5
|
Release files / livekit_memorysync-1.1.0-py3-none-any.whl
| Download URL | livekit_memorysync-1.1.0-py3-none-any.whl |
|---|---|
| Size | 14.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bdf0d3f8a0d32bdf0b299c74ff4a0c0dc67ba454bdff3c7e9740a5e66777db20
|
|
BLAKE2b-256 checksum How to use checksums |
deab5d1d6db19668fcaa3cc8bc337c5a18e0f1d7bf594a61111ea27bcb94e0db
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.5
|