elevenlabs-memorysync 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ venv/
2
+ dist/
3
+ *.egg-info/
4
+ __pycache__/
5
+ .pytest_cache/
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.5
2
+ Name: elevenlabs-memorysync
3
+ Version: 1.0.0
4
+ Summary: MemorySync for ElevenLabs Agents: a memory-injecting OpenAI-compatible LLM proxy with a hard recall budget, phone-caller identity via prompt tags, HMAC-verified post-call transcript capture, and idempotent storage that never duplicates a turn.
5
+ Project-URL: Homepage, https://memorysync.io
6
+ Project-URL: Documentation, https://docs.memorysync.io/guides/elevenlabs
7
+ Author-email: MemorySync <support@memorysync.io>
8
+ License-Expression: MIT
9
+ Keywords: agents,conversational-ai,elevenlabs,llm,memory,proxy,voice
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Communications :: Telephony
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: fastapi>=0.100
20
+ Requires-Dist: httpx<1,>=0.25
21
+ Description-Content-Type: text/markdown
22
+
23
+ # elevenlabs-memorysync
24
+
25
+ [MemorySync](https://memorysync.io) for [ElevenLabs Agents](https://elevenlabs.io/docs/eleven-agents/overview) —
26
+ voice agents that remember callers across calls, on the web **and on the phone**.
27
+
28
+ ```bash
29
+ pip install elevenlabs-memorysync
30
+ ```
31
+
32
+ ## Three tiers, one package
33
+
34
+ | Tier | What runs | Memory freshness | Code needed |
35
+ | --- | --- | --- | --- |
36
+ | **0 — Zero code** | Nothing — an ElevenLabs *webhook tool* calls the MemorySync REST API directly | When the LLM decides to look | None (dashboard only) |
37
+ | **1 — No proxy** | `fetch_memory_variables` at session start + the post-call webhook receiver | Start of call | ~5 lines |
38
+ | **2 — Full proxy** | `create_proxy_app` as the agent's Custom LLM | **Every turn**, under a hard budget | ~3 lines |
39
+
40
+ ## Tier 2 — the memory proxy (recommended)
41
+
42
+ An OpenAI-compatible `/v1/chat/completions` server that injects memories into
43
+ every request and captures both sides of the conversation — with guarantees
44
+ none of the copy-paste integrations offer:
45
+
46
+ - **Hard recall budget** (default **1.2s**): a slow or dead memory backend
47
+ means an unenriched request, never a delayed voice reply.
48
+ - **Any upstream LLM**: OpenAI, Azure OpenAI, Groq, Gemini's OpenAI-compatible
49
+ endpoint, a LiteLLM gateway — one `upstream_base_url` away.
50
+ - **Phone-caller identity** via prompt tags (below) — the only mechanism that
51
+ works for Twilio/SIP calls, not just browser sessions.
52
+ - **Byte-faithful SSE relay**: upstream chunks (tool-call deltas included) are
53
+ forwarded verbatim, so system tools like `end_call` keep working.
54
+ - **Idempotent capture**: deterministic seeds mean retries and the post-call
55
+ webhook sweep can never duplicate a turn.
56
+
57
+ ```python
58
+ # server.py
59
+ from elevenlabs_memorysync import create_proxy_app
60
+
61
+ app = create_proxy_app(
62
+ api_key="ms_...", # MemorySync (or MEMORYSYNC_API_KEY)
63
+ upstream_api_key="sk-...", # your LLM key (or OPENAI_API_KEY)
64
+ # upstream_base_url="https://api.groq.com/openai/v1", # any provider
65
+ proxy_api_key="a-long-random-secret", # what ElevenLabs must present
66
+ )
67
+ # uvicorn server:app --host 0.0.0.0 --port 8013
68
+ ```
69
+
70
+ In the ElevenLabs agent: **LLM → Custom LLM**, Server URL = your deployment's
71
+ public URL, Model ID = the upstream model (e.g. `gpt-4o-mini`), API key = the
72
+ `proxy_api_key` value.
73
+
74
+ ### Identity: how the proxy knows who is calling
75
+
76
+ The proxy resolves the caller in this order — and **stores nothing when no
77
+ identity resolves** (a passthrough call can never pollute another user's
78
+ memory):
79
+
80
+ 1. `elevenlabs_extra_body.user_id` — pass `customLlmExtraBody: { user_id }`
81
+ from your web/SDK session (enable *Custom LLM extra body* in the agent's
82
+ Security tab).
83
+ 2. **Prompt tag** — add one line to the agent's system prompt:
84
+
85
+ ```
86
+ memorysync-user: {{system__caller_id}}
87
+ ```
88
+
89
+ ElevenLabs interpolates the caller's phone number; the proxy extracts the
90
+ line, resolves identity, and **strips it before the model ever sees it**.
91
+ Works for phone calls, the widget, and every SDK — zero client code. Any
92
+ dynamic variable works (`{{user_id}}`, `{{system__caller_id}}`, …), and an
93
+ optional `memorysync-conversation: {{system__conversation_id}}` line scopes
94
+ the transcript per call.
95
+ 3. `default_user_id=` — explicit single-user fallback, off by default.
96
+
97
+ ## Tier 1 — session-start memory + post-call capture
98
+
99
+ ```python
100
+ from elevenlabs_memorysync import fetch_memory_variables
101
+
102
+ variables = await fetch_memory_variables("caller-42") # {"memorysync_context": "..."}
103
+ # pass as dynamic_variables at session start; prompt contains {{memorysync_context}}
104
+ ```
105
+
106
+ Capture at call end — deploy the webhook receiver and set the URL in
107
+ Agents → Settings → **Post-call webhooks**:
108
+
109
+ ```python
110
+ from elevenlabs_memorysync import create_webhook_app
111
+
112
+ app = create_webhook_app(
113
+ api_key="ms_...",
114
+ webhook_secret="wsec_...", # or ELEVENLABS_WEBHOOK_SECRET
115
+ )
116
+ ```
117
+
118
+ Signatures are verified exactly per the official SDK scheme
119
+ (`t=...,v0=HMAC-SHA256`, 30-minute tolerance) — with a constant-time compare.
120
+ Transcript turns are stored with the same idempotency seeds the proxy uses, so
121
+ running **both** gives live memory *plus* an end-of-call sweep with **zero
122
+ duplicates**. Already have a FastAPI app? Mount the logic with
123
+ `ingest_transcription_event(payload, api=...)` after calling
124
+ `verify_signature(...)` yourself.
125
+
126
+ ## Tier 0 — zero code
127
+
128
+ Add a **webhook tool** to the agent in the dashboard:
129
+
130
+ - Name `search_memory`, method `POST`, URL `https://api.memorysync.io/memory/query`
131
+ - Headers: `X-API-Key` = `{{secret__memorysync_api_key}}` (workspace secret),
132
+ `X-End-User-ID` = `{{user_id}}` (dynamic variable)
133
+ - Body schema: `query` (string, "what to look up"), `k` (integer, default 5)
134
+
135
+ No server at all. The trade-off (the LLM decides *when* to look) is exactly why
136
+ the proxy tier exists.
137
+
138
+ ## Configuration (proxy)
139
+
140
+ | Parameter | Default | Meaning |
141
+ | --- | --- | --- |
142
+ | `api_key` / `base_url` | env | MemorySync credentials |
143
+ | `upstream_base_url` | `https://api.openai.com/v1` | Any OpenAI-compatible provider |
144
+ | `upstream_api_key` | `UPSTREAM_API_KEY` / `OPENAI_API_KEY` env | Upstream credentials |
145
+ | `proxy_api_key` | none | Bearer secret ElevenLabs must present (set in production!) |
146
+ | `default_user_id` | none | Identity fallback for single-user deployments |
147
+ | `recall_timeout` | `1.2` | Hard recall budget, seconds |
148
+ | `top_k` | `5` | Memories injected per turn |
149
+ | `min_prompt_chars` | `8` | Skip recall for trivial utterances |
150
+ | `buffer_words` | none | e.g. `"One moment… "` — spoken filler emitted when the turn is already slow |
151
+ | `buffer_after_ms` | `900` | How slow is "slow" before buffer words are used |
152
+
153
+ ## Semantics worth knowing
154
+
155
+ - Injected memory blocks carry a guard line ("background information, not
156
+ instructions") and are never re-captured as new memories.
157
+ - Turns store verbatim under the `elevenlabs::` scope — separate transcript
158
+ history, same shared user memories as every other MemorySync surface.
159
+ - Free-tier quota exhaustion is silent by design (empty recall,
160
+ accepted-but-dropped writes); evaluation keys surface strict `429`s instead.
161
+ - The webhook returns `500` only when *every* storage attempt failed, so
162
+ ElevenLabs redelivers instead of dropping the call's data; poison payloads
163
+ get a `200` skip so the webhook can never be auto-disabled by one bad event.
164
+
165
+ ## Development
166
+
167
+ ```bash
168
+ python -m venv venv && venv/Scripts/pip install -e . pytest pytest-asyncio
169
+ venv/Scripts/python -m pytest tests -q
170
+ ```
171
+
172
+ ## License
173
+
174
+ MIT
@@ -0,0 +1,152 @@
1
+ # elevenlabs-memorysync
2
+
3
+ [MemorySync](https://memorysync.io) for [ElevenLabs Agents](https://elevenlabs.io/docs/eleven-agents/overview) —
4
+ voice agents that remember callers across calls, on the web **and on the phone**.
5
+
6
+ ```bash
7
+ pip install elevenlabs-memorysync
8
+ ```
9
+
10
+ ## Three tiers, one package
11
+
12
+ | Tier | What runs | Memory freshness | Code needed |
13
+ | --- | --- | --- | --- |
14
+ | **0 — Zero code** | Nothing — an ElevenLabs *webhook tool* calls the MemorySync REST API directly | When the LLM decides to look | None (dashboard only) |
15
+ | **1 — No proxy** | `fetch_memory_variables` at session start + the post-call webhook receiver | Start of call | ~5 lines |
16
+ | **2 — Full proxy** | `create_proxy_app` as the agent's Custom LLM | **Every turn**, under a hard budget | ~3 lines |
17
+
18
+ ## Tier 2 — the memory proxy (recommended)
19
+
20
+ An OpenAI-compatible `/v1/chat/completions` server that injects memories into
21
+ every request and captures both sides of the conversation — with guarantees
22
+ none of the copy-paste integrations offer:
23
+
24
+ - **Hard recall budget** (default **1.2s**): a slow or dead memory backend
25
+ means an unenriched request, never a delayed voice reply.
26
+ - **Any upstream LLM**: OpenAI, Azure OpenAI, Groq, Gemini's OpenAI-compatible
27
+ endpoint, a LiteLLM gateway — one `upstream_base_url` away.
28
+ - **Phone-caller identity** via prompt tags (below) — the only mechanism that
29
+ works for Twilio/SIP calls, not just browser sessions.
30
+ - **Byte-faithful SSE relay**: upstream chunks (tool-call deltas included) are
31
+ forwarded verbatim, so system tools like `end_call` keep working.
32
+ - **Idempotent capture**: deterministic seeds mean retries and the post-call
33
+ webhook sweep can never duplicate a turn.
34
+
35
+ ```python
36
+ # server.py
37
+ from elevenlabs_memorysync import create_proxy_app
38
+
39
+ app = create_proxy_app(
40
+ api_key="ms_...", # MemorySync (or MEMORYSYNC_API_KEY)
41
+ upstream_api_key="sk-...", # your LLM key (or OPENAI_API_KEY)
42
+ # upstream_base_url="https://api.groq.com/openai/v1", # any provider
43
+ proxy_api_key="a-long-random-secret", # what ElevenLabs must present
44
+ )
45
+ # uvicorn server:app --host 0.0.0.0 --port 8013
46
+ ```
47
+
48
+ In the ElevenLabs agent: **LLM → Custom LLM**, Server URL = your deployment's
49
+ public URL, Model ID = the upstream model (e.g. `gpt-4o-mini`), API key = the
50
+ `proxy_api_key` value.
51
+
52
+ ### Identity: how the proxy knows who is calling
53
+
54
+ The proxy resolves the caller in this order — and **stores nothing when no
55
+ identity resolves** (a passthrough call can never pollute another user's
56
+ memory):
57
+
58
+ 1. `elevenlabs_extra_body.user_id` — pass `customLlmExtraBody: { user_id }`
59
+ from your web/SDK session (enable *Custom LLM extra body* in the agent's
60
+ Security tab).
61
+ 2. **Prompt tag** — add one line to the agent's system prompt:
62
+
63
+ ```
64
+ memorysync-user: {{system__caller_id}}
65
+ ```
66
+
67
+ ElevenLabs interpolates the caller's phone number; the proxy extracts the
68
+ line, resolves identity, and **strips it before the model ever sees it**.
69
+ Works for phone calls, the widget, and every SDK — zero client code. Any
70
+ dynamic variable works (`{{user_id}}`, `{{system__caller_id}}`, …), and an
71
+ optional `memorysync-conversation: {{system__conversation_id}}` line scopes
72
+ the transcript per call.
73
+ 3. `default_user_id=` — explicit single-user fallback, off by default.
74
+
75
+ ## Tier 1 — session-start memory + post-call capture
76
+
77
+ ```python
78
+ from elevenlabs_memorysync import fetch_memory_variables
79
+
80
+ variables = await fetch_memory_variables("caller-42") # {"memorysync_context": "..."}
81
+ # pass as dynamic_variables at session start; prompt contains {{memorysync_context}}
82
+ ```
83
+
84
+ Capture at call end — deploy the webhook receiver and set the URL in
85
+ Agents → Settings → **Post-call webhooks**:
86
+
87
+ ```python
88
+ from elevenlabs_memorysync import create_webhook_app
89
+
90
+ app = create_webhook_app(
91
+ api_key="ms_...",
92
+ webhook_secret="wsec_...", # or ELEVENLABS_WEBHOOK_SECRET
93
+ )
94
+ ```
95
+
96
+ Signatures are verified exactly per the official SDK scheme
97
+ (`t=...,v0=HMAC-SHA256`, 30-minute tolerance) — with a constant-time compare.
98
+ Transcript turns are stored with the same idempotency seeds the proxy uses, so
99
+ running **both** gives live memory *plus* an end-of-call sweep with **zero
100
+ duplicates**. Already have a FastAPI app? Mount the logic with
101
+ `ingest_transcription_event(payload, api=...)` after calling
102
+ `verify_signature(...)` yourself.
103
+
104
+ ## Tier 0 — zero code
105
+
106
+ Add a **webhook tool** to the agent in the dashboard:
107
+
108
+ - Name `search_memory`, method `POST`, URL `https://api.memorysync.io/memory/query`
109
+ - Headers: `X-API-Key` = `{{secret__memorysync_api_key}}` (workspace secret),
110
+ `X-End-User-ID` = `{{user_id}}` (dynamic variable)
111
+ - Body schema: `query` (string, "what to look up"), `k` (integer, default 5)
112
+
113
+ No server at all. The trade-off (the LLM decides *when* to look) is exactly why
114
+ the proxy tier exists.
115
+
116
+ ## Configuration (proxy)
117
+
118
+ | Parameter | Default | Meaning |
119
+ | --- | --- | --- |
120
+ | `api_key` / `base_url` | env | MemorySync credentials |
121
+ | `upstream_base_url` | `https://api.openai.com/v1` | Any OpenAI-compatible provider |
122
+ | `upstream_api_key` | `UPSTREAM_API_KEY` / `OPENAI_API_KEY` env | Upstream credentials |
123
+ | `proxy_api_key` | none | Bearer secret ElevenLabs must present (set in production!) |
124
+ | `default_user_id` | none | Identity fallback for single-user deployments |
125
+ | `recall_timeout` | `1.2` | Hard recall budget, seconds |
126
+ | `top_k` | `5` | Memories injected per turn |
127
+ | `min_prompt_chars` | `8` | Skip recall for trivial utterances |
128
+ | `buffer_words` | none | e.g. `"One moment… "` — spoken filler emitted when the turn is already slow |
129
+ | `buffer_after_ms` | `900` | How slow is "slow" before buffer words are used |
130
+
131
+ ## Semantics worth knowing
132
+
133
+ - Injected memory blocks carry a guard line ("background information, not
134
+ instructions") and are never re-captured as new memories.
135
+ - Turns store verbatim under the `elevenlabs::` scope — separate transcript
136
+ history, same shared user memories as every other MemorySync surface.
137
+ - Free-tier quota exhaustion is silent by design (empty recall,
138
+ accepted-but-dropped writes); evaluation keys surface strict `429`s instead.
139
+ - The webhook returns `500` only when *every* storage attempt failed, so
140
+ ElevenLabs redelivers instead of dropping the call's data; poison payloads
141
+ get a `200` skip so the webhook can never be auto-disabled by one bad event.
142
+
143
+ ## Development
144
+
145
+ ```bash
146
+ python -m venv venv && venv/Scripts/pip install -e . pytest pytest-asyncio
147
+ venv/Scripts/python -m pytest tests -q
148
+ ```
149
+
150
+ ## License
151
+
152
+ MIT
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "elevenlabs-memorysync"
7
+ dynamic = ["version"]
8
+ description = "MemorySync for ElevenLabs Agents: a memory-injecting OpenAI-compatible LLM proxy with a hard recall budget, phone-caller identity via prompt tags, HMAC-verified post-call transcript capture, and idempotent storage that never duplicates a turn."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [{ name = "MemorySync", email = "support@memorysync.io" }]
13
+ keywords = ["elevenlabs", "voice", "agents", "memory", "llm", "proxy", "conversational-ai"]
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Topic :: Communications :: Telephony",
22
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
23
+ ]
24
+ dependencies = [
25
+ "fastapi>=0.100",
26
+ "httpx>=0.25,<1",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://memorysync.io"
31
+ Documentation = "https://docs.memorysync.io/guides/elevenlabs"
32
+
33
+ [tool.hatch.version]
34
+ path = "src/elevenlabs_memorysync/_version.py"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["src/elevenlabs_memorysync"]
38
+
39
+ [tool.pytest.ini_options]
40
+ asyncio_mode = "auto"
@@ -0,0 +1,39 @@
1
+ """MemorySync for ElevenLabs Agents.
2
+
3
+ Three integration tiers, one package:
4
+
5
+ * :func:`create_proxy_app` — the flagship: an OpenAI-compatible LLM
6
+ proxy with a hard recall budget, phone-caller identity via prompt
7
+ tags, byte-faithful SSE relay, and idempotent live capture.
8
+ * :func:`create_webhook_app` / :func:`ingest_transcription_event` —
9
+ HMAC-verified post-call transcript capture that converges with the
10
+ proxy's live writes (zero duplicates).
11
+ * :func:`fetch_memory_variables` — session-start context as ElevenLabs
12
+ dynamic variables, for deployments without a proxy.
13
+ """
14
+
15
+ from ._api import MemorySyncAPIError, fnv1a64
16
+ from ._version import __version__
17
+ from .proxy import DEFAULT_MEMORY_HEADER, GUARD_LINE, create_proxy_app
18
+ from .variables import fetch_memory_variables, fetch_memory_variables_sync
19
+ from .webhook import (
20
+ WebhookVerificationError,
21
+ create_webhook_app,
22
+ ingest_transcription_event,
23
+ verify_signature,
24
+ )
25
+
26
+ __all__ = [
27
+ "__version__",
28
+ "create_proxy_app",
29
+ "create_webhook_app",
30
+ "ingest_transcription_event",
31
+ "verify_signature",
32
+ "WebhookVerificationError",
33
+ "fetch_memory_variables",
34
+ "fetch_memory_variables_sync",
35
+ "MemorySyncAPIError",
36
+ "fnv1a64",
37
+ "DEFAULT_MEMORY_HEADER",
38
+ "GUARD_LINE",
39
+ ]