Overview
Conversation Memory gives agents persistent, cross-session context about customers. A Memory Store holds profiles, observations, summaries, and traits. Data enters two ways:
Automatic: Link a Conversation Orchestrator config to a store. Profiles are auto-created per caller. At conversation close, observations and summaries are extracted from the transcript and saved. Conflicting data is automatically reconciled.
Direct API: POST observations, summaries, or traits yourself for any use case.
Data comes out via Recall — hybrid semantic + lexical search that surfaces the most relevant profile data. For voice agents where latency is critical, use GET /Observations directly instead.
Base URL: https://memory.twilio.com
Authentication: HTTP Basic — Authorization: Basic {base64(accountSid:authToken)}
Rules for agents:
- Always poll
statusUrlafter any 202 response — all writes are async - Always create a trait group before writing traits of that group
- Always use E.164 format for phone numbers (
+15558675310) - Always verify store status is
ACTIVEbefore operating on it - Never use
/v1/Services/paths — correct paths are/v1/ControlPlane/Stores(config) and/v1/Stores/{storeId}/...(data) - Never exceed 10 observations per batch or 4096 characters per observation
- Never assume writes are immediately readable — indexing is async
- Never send PCI or HIPAA data — not yet compliant
Prerequisites
- Twilio account with Account SID and Auth Token
— A credit card must be added to the account
— See
twilio-account-setupfor initial setup — Seetwilio-iam-auth-setupfor credential best practices - Environment variables:
TWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKEN
- SDK:
pip install twilio/npm install twilio - For automatic extraction: a Conversation Orchestrator configuration — see
twilio-conversation-orchestrator
Quickstart
Step 1 — Create a Memory Store
Python
import os, requests
from requests.auth import HTTPBasicAuth
account_sid = os.environ["TWILIO_ACCOUNT_SID"]
auth_token = os.environ["TWILIO_AUTH_TOKEN"]
base_url = "https://memory.twilio.com"
auth = HTTPBasicAuth(account_sid, auth_token)
store = requests.post(
f"{base_url}/v1/ControlPlane/Stores",
auth=auth,
json={"displayName": "my-store", "description": "Customer memory"}
).json()
print(store["id"]) # mem_store_xxx — save this
# Poll store["statusUrl"] until status is ACTIVE
Node.js
const axios = require("axios");
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const baseUrl = "https://memory.twilio.com";
const auth = { username: accountSid, password: authToken };
const { data: store } = await axios.post(
`${baseUrl}/v1/ControlPlane/Stores`,
{ displayName: "my-store", description: "Customer memory" },
{ auth }
);
console.log(store.id); // mem_store_xxx
// Poll store.statusUrl until status is ACTIVE
Step 2 — Create a profile with identity resolution
Python
profile = requests.post(
f"{base_url}/v1/Stores/{store['id']}/Profiles",
auth=auth,
json={"traits": {"contact": {"phone": "+15558675310", "name": "Alex"}}}
).json()
profile_id = profile["id"] # mem_profile_xxx
Node.js
const { data: profile } = await axios.post(
`${baseUrl}/v1/Stores/${store.id}/Profiles`,
{ traits: { contact: { phone: "+15558675310", name: "Alex" } } },
{ auth }
);
const profileId = profile.id; // mem_profile_xxx
Step 3 — Post observations (quick test)
Post an observation directly to quickly test Recall in step 4. In production, the preferred path is connecting your Memory Store to a Conversation Orchestrator configuration — observations are then automatically extracted from conversations without manual POSTs. See twilio-conversation-orchestrator.
Python
requests.post(
f"{base_url}/v1/Stores/{store['id']}/Profiles/{profile_id}/Observations",
auth=auth,
json={"observations": [{
"content": "Customer prefers email communication over phone calls",
"source": "support-agent",
"occurredAt": "2025-01-15T10:30:00Z"
}]}
)
# 202 — poll statusUrl for completion
Node.js
await axios.post(
`${baseUrl}/v1/Stores/${store.id}/Profiles/${profileId}/Observations`,
{ observations: [{
content: "Customer prefers email communication over phone calls",
source: "support-agent",
occurredAt: "2025-01-15T10:30:00Z",
}] },
{ auth }
);
// 202 — poll statusUrl for completion
Step 4 — Recall relevant context
Python
recall = requests.post(
f"{base_url}/v1/Stores/{store['id']}/Profiles/{profile_id}/Recall",
auth=auth,
json={"query": "communication preferences"}
).json()
for obs in recall["observations"]:
print(f"[{obs['score']:.2f}] {obs['content']}")
Node.js
const { data: recall } = await axios.post(
`${baseUrl}/v1/Stores/${store.id}/Profiles/${profileId}/Recall`,
{ query: "communication preferences" },
{ auth }
);
for (const obs of recall.observations) {
console.log(`[${obs.score.toFixed(2)}] ${obs.content}`);
}
Key Patterns
Recall Modes
| Mode | Request | When to use |
|---|---|---|
| Explicit query | {"query": "shipping preferences"} |
You control relevance |
| Conversation context | {"conversationId": "conv_conversation_xxx"} |
Active Orchestrator conversation — auto-generates query from last 10 messages |
| Most recent | {} (omit both) |
Chronological order, no relevance ranking |
Additional Recall parameters: observationsLimit (default 20, 0–100), summariesLimit (default 5), communicationsLimit (default 0, requires conversationId), relevanceThreshold (0–1), beginDate/endDate.
Profile Resolution
| I have... | Use | Behavior |
|---|---|---|
| Profile ID | PATCH /v1/Stores/{storeId}/Profiles/{profileId} |
Direct update |
| An identifier (phone, email) | POST /v1/Stores/{storeId}/Profiles |
Identity resolution finds or creates |
| Bulk contact data | PUT /v1/Stores/{storeId}/Profiles/Bulk |
Up to 1000, identity-resolved |
| CSV file | POST /v1/Stores/{storeId}/Profiles/Imports |
Presigned URL upload |
Check for 308 redirects on profile endpoints — indicates a merged profile.
Trait Groups
Every trait must belong to a trait group. You must create the group (or add the trait to an existing group) before writing trait values to a profile.
Each trait has a dataType (STRING, NUMBER, BOOLEAN, ARRAY). Set idTypePromotion on a trait (e.g., "phone") to make it an identifier for Lookup and identity resolution.
Add a new trait to an existing group:
Use PATCH /v1/ControlPlane/Stores/{storeId}/TraitGroups/{traitGroupName} — pass the new trait definition in the request body. Only the new traits need to be included; existing traits are unchanged.
Python
requests.patch(
f"{base_url}/v1/ControlPlane/Stores/{store['id']}/TraitGroups/contact",
auth=auth,
json={"traits": {
"preferred_channel": {"dataType": "STRING", "description": "Preferred contact method"}
}}
)
# 202 — adds "preferred_channel" to the existing "contact" group
Node.js
await axios.patch(
`${baseUrl}/v1/ControlPlane/Stores/${store.id}/TraitGroups/contact`,
{ traits: {
preferred_channel: { dataType: "STRING", description: "Preferred contact method" },
} },
{ auth }
);
// 202 — adds "preferred_channel" to the existing "contact" group
To remove a trait from a group, PATCH with "dataType": "" for that trait name.
Automatic Extraction (Orchestrator Integration)
- Create a Memory Store
- Create a Conversation Orchestrator Configuration with capture rules
- Link the store to the config
Once linked: profiles are auto-created per caller, and extraction is tied to the conversation lifecycle configured in Orchestrator. By default, observations are extracted when a conversation goes inactive or ends. Conversation summaries are generated when a conversation ends. These lifecycle transitions are configurable in your Orchestrator configuration — how you define conversation status timeouts determines when memory extraction runs. Conflicting information is automatically reconciled. See twilio-conversation-orchestrator.
Lookup by Identifier
Python
result = requests.post(
f"{base_url}/v1/Stores/{store['id']}/Profiles/Lookup",
auth=auth,
json={"idType": "phone", "value": "+15558675310"}
).json()
profile_id = result["profiles"][0]["id"]
Node.js
const { data: result } = await axios.post(
`${baseUrl}/v1/Stores/${store.id}/Profiles/Lookup`,
{ idType: "phone", value: "+15558675310" },
{ auth }
);
const profileId = result.profiles[0].id;
Voice Agent Latency Optimization
For voice agents where latency matters, skip semantic search and fetch observations directly:
Python
observations = requests.get(
f"{base_url}/v1/Stores/{store['id']}/Profiles/{profile_id}/Observations",
auth=auth,
params={"source": "support-agent", "createdAfter": "2025-01-01T00:00:00Z"}
).json()
Node.js
const { data: observations } = await axios.get(
`${baseUrl}/v1/Stores/${store.id}/Profiles/${profileId}/Observations`,
{ auth, params: { source: "support-agent", createdAfter: "2025-01-01T00:00:00Z" } }
);
Async Polling Pattern
All 202 responses include a statusUrl. Poll until status is COMPLETED or FAILED:
Python
import time
status_url = store["statusUrl"]
while True:
op = requests.get(status_url, auth=auth).json()
if op["status"] in ("COMPLETED", "FAILED", "CANCELLED"):
break
time.sleep(2)
Node.js
let op;
do {
await new Promise(r => setTimeout(r, 2000));
op = (await axios.get(store.statusUrl, { auth })).data;
} while (!["COMPLETED", "FAILED", "CANCELLED"].includes(op.status));
CANNOT
- Cannot exceed 15 Memory Stores per account — use sub-accounts beyond this
- Cannot batch more than 10 observations per request
- Cannot exceed 4096 characters per observation
- Cannot write traits to a group that doesn't exist — create the trait group first
- Cannot recover deleted profiles or observations — deletion is irreversible
- Cannot read data immediately after write — indexing is async
- Cannot auto-extract observations without a linked Orchestrator config
- Cannot exceed 1000 profiles per bulk upsert
- Cannot include auth headers when uploading to presigned import URLs — they're pre-signed
- Cannot use spaces or underscores in store displayName — pattern is
^[a-zA-Z0-9-]+$
Next Steps
- Automatic conversation capture:
twilio-conversation-orchestrator - Background transcript intelligence (script adherence, NBR, scoring):
twilio-conversation-intelligence - Enterprise knowledge retrieval (RAG):
twilio-enterprise-knowledge - Voice agent with ConversationRelay:
twilio-voice-conversation-relay - TAC SDK middleware:
twilio-agent-connect - Debug issues:
twilio-debugging-observability