elevenlabs-memorysync
MemorySync for ElevenLabs Agents — voice agents that remember callers across calls, on the web and on the phone.
pip install elevenlabs-memorysync
Three tiers, one package
| Tier | What runs | Memory freshness | Code needed |
|---|---|---|---|
| 0 — Zero code | Nothing — an ElevenLabs webhook tool calls the MemorySync REST API directly | When the LLM decides to look | None (dashboard only) |
| 1 — No proxy | fetch_memory_variables at session start + the post-call webhook receiver |
Start of call | ~5 lines |
| 2 — Full proxy | create_proxy_app as the agent's Custom LLM |
Every turn, under a hard budget | ~3 lines |
Tier 2 — the memory proxy (recommended)
An OpenAI-compatible /v1/chat/completions server that injects memories into
every request and sends each of the caller's turns to fact extraction — with
guarantees none of the copy-paste integrations offer:
- Hard recall budget (default 1.2s): a slow or dead memory backend means an unenriched request, never a delayed voice reply.
- Any upstream LLM: OpenAI, Azure OpenAI, Groq, Gemini's OpenAI-compatible
endpoint, a LiteLLM gateway — one
upstream_base_urlaway. - Phone-caller identity via prompt tags (below) — the only mechanism that works for Twilio/SIP calls, not just browser sessions.
- Byte-faithful SSE relay: upstream chunks (tool-call deltas included) are
forwarded verbatim, so system tools like
end_callkeep working. - Idempotent capture: deterministic seeds mean retries and the post-call webhook sweep are recognised server-side — a turn is never extracted twice.
- Facts, not transcripts: only the caller's turns are sent, and only the durable facts in them (preferences, plans, details) are stored as memories. The model's replies are relayed to ElevenLabs but not sent to MemorySync — assistant replies are not stored as memories.
# server.py
from elevenlabs_memorysync import create_proxy_app
app = create_proxy_app(
api_key="ms_...", # MemorySync (or MEMORYSYNC_API_KEY)
upstream_api_key="sk-...", # your LLM key (or OPENAI_API_KEY)
# upstream_base_url="https://api.groq.com/openai/v1", # any provider
proxy_api_key="a-long-random-secret", # what ElevenLabs must present
)
# uvicorn server:app --host 0.0.0.0 --port 8013
In the ElevenLabs agent: LLM → Custom LLM, Server URL = your deployment's
public URL, Model ID = the upstream model (e.g. gpt-4o-mini), API key = the
proxy_api_key value.
Identity: how the proxy knows who is calling
The proxy resolves the caller in this order — and sends nothing to MemorySync when no identity resolves (a passthrough call can never pollute another user's memory):
-
elevenlabs_extra_body.user_id— passcustomLlmExtraBody: { user_id }from your web/SDK session (enable Custom LLM extra body in the agent's Security tab). -
Prompt tag — add one line to the agent's system prompt:
memorysync-user: {{system__caller_id}}ElevenLabs interpolates the caller's phone number; the proxy extracts the line, resolves identity, and strips it before the model ever sees it. Works for phone calls, the widget, and every SDK — zero client code. Any dynamic variable works (
{{user_id}},{{system__caller_id}}, …), and an optionalmemorysync-conversation: {{system__conversation_id}}line tags the facts from each call withsession_id: "elevenlabs::<conversation id>". -
default_user_id=— explicit single-user fallback, off by default.
Tier 1 — session-start memory + post-call capture
from elevenlabs_memorysync import fetch_memory_variables
variables = await fetch_memory_variables("caller-42") # {"memorysync_context": "..."}
# pass as dynamic_variables at session start; prompt contains {{memorysync_context}}
Capture at call end — deploy the webhook receiver and set the URL in Agents → Settings → Post-call webhooks:
from elevenlabs_memorysync import create_webhook_app
app = create_webhook_app(
api_key="ms_...",
webhook_secret="wsec_...", # or ELEVENLABS_WEBHOOK_SECRET
)
Signatures are verified exactly per the official SDK scheme
(t=...,v0=HMAC-SHA256, 30-minute tolerance) — with a constant-time compare.
The caller's turns from the transcript are sent to fact extraction with the
same idempotency seeds the proxy uses, so running both gives live memory
plus an end-of-call sweep: it catches any turn the live path missed, and a
turn the proxy already sent is recognised and never extracted twice. The
agent's turns are not sent. Already have a FastAPI app? Mount the logic with
ingest_transcription_event(payload, api=...) after calling
verify_signature(...) yourself. It returns a summary of the call's
transcript entries — no memory ids, since facts are extracted asynchronously
and get their own:
{"status": "ok", "sent": 1, "stored": 1, "already": 0, "skipped": 2, "errors": 0,
"conversation_id": "conv_...", "user_id": "caller-42"}
sent counts user turns accepted for fact extraction (stored carries the
same count, for code written against earlier versions); already, user turns
MemorySync had already received (from the proxy or an earlier delivery);
skipped, agent turns and blank or malformed entries (not sent) plus user
turns with nothing to extract (filler, monthly quota reached); errors, user
turns that could not be sent or whose extraction could not run.
Tier 0 — zero code
Add a webhook tool to the agent in the dashboard:
- Name
search_memory, methodPOST, URLhttps://api.memorysync.io/memory/query - Headers:
X-API-Key={{secret__memorysync_api_key}}(workspace secret),X-End-User-ID={{user_id}}(dynamic variable) - Body schema:
query(string, "what to look up"),k(integer, default 5)
No server at all. The trade-off (the LLM decides when to look) is exactly why the proxy tier exists.
Configuration (proxy)
| Parameter | Default | Meaning |
|---|---|---|
api_key / base_url |
env | MemorySync credentials |
upstream_base_url |
https://api.openai.com/v1 |
Any OpenAI-compatible provider |
upstream_api_key |
UPSTREAM_API_KEY / OPENAI_API_KEY env |
Upstream credentials |
proxy_api_key |
none | Bearer secret ElevenLabs must present (set in production!) |
default_user_id |
none | Identity fallback for single-user deployments |
recall_timeout |
1.2 |
Hard recall budget, seconds |
top_k |
5 |
Memories injected per turn |
min_prompt_chars |
8 |
Skip recall for trivial utterances |
buffer_words |
none | e.g. "One moment… " — spoken filler emitted when the turn is already slow |
buffer_after_ms |
900 |
How slow is "slow" before buffer words are used |
Semantics worth knowing
- Injected memory blocks carry a guard line ("background information, not instructions") and are never sent back to MemorySync as a turn.
- Only the caller's turns are sent, as plain text with
role: "user". The server extracts the durable facts they contain and stores only those, tagged withsession_id: "elevenlabs::<conversation id>"— the same shared user memories as every other MemorySync surface. The agent's replies are not sent, and 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. - The webhook returns
500only when every send failed (MemorySync unreachable, or extraction could not run), so ElevenLabs redelivers instead of dropping the caller's turns; poison payloads get a200skip so the webhook can never be auto-disabled by one bad event.
Development
python -m venv venv && venv/Scripts/pip install -e . pytest pytest-asyncio
venv/Scripts/python -m pytest tests -q
License
MIT
Metadata
Release files for elevenlabs-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 | Provenance | |
|---|---|---|---|---|
| elevenlabs_memorysync-1.1.0.tar.gz | 29.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| elevenlabs_memorysync-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 52.0 kB
Release files / elevenlabs_memorysync-1.1.0.tar.gz
| Download URL | elevenlabs_memorysync-1.1.0.tar.gz |
|---|---|
| Size | 29.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
31f2d48131e276926370ef23f016e830aea201c1260a9aaa51733c0452b69f53
|
|
BLAKE2b-256 checksum How to use checksums |
e2ee8f26e9a35fc5ff673b09dc3cca9d8431aee49a24827ce545e8e8f5987fd9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.5
|
Release files / elevenlabs_memorysync-1.1.0-py3-none-any.whl
| Download URL | elevenlabs_memorysync-1.1.0-py3-none-any.whl |
|---|---|
| Size | 22.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d2f4f5f1e638dbb4d40a665e85213175f25fedf9df49294a60b95cd776848dfa
|
|
BLAKE2b-256 checksum How to use checksums |
b7a8f6ddd69cf3eeb8c07626fd483a57e910beaeb5f6e12e0ed08195504a9cfb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.5
|
Security for elevenlabs-memorysync 1.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.