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 |
"default" |
Scope memories to one room/call thread (livekit::<thread_id>) |
recall_timeout |
1.2 |
Hard budget (seconds) for recall injection |
top_k |
6 |
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.1
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.1.tar.gz | 17.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| livekit_memorysync-1.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.9 kB
Release files / livekit_memorysync-1.1.1.tar.gz
| Download URL | livekit_memorysync-1.1.1.tar.gz |
|---|---|
| Size | 17.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b78cdd3f5a7ebb97267f9737a78401b0b21b3117fe58d8895a9608aa55d8e6a0
|
|
BLAKE2b-256 checksum How to use checksums |
021fab4b28e9f3237406b5f98769018d72c450bac4b851e44b8a8f62af4f3c47
|
| 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.1-py3-none-any.whl
| Download URL | livekit_memorysync-1.1.1-py3-none-any.whl |
|---|---|
| Size | 14.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eee53c20b9730581a5395d881f937aaacc223fc7116c79669354a04ad1a8f679
|
|
BLAKE2b-256 checksum How to use checksums |
436055b1431e918413c14b326b3dfa12511eaec3c0b6032673e97561e5d336b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.5
|