Caller Memory & Personalization (returning callers)

Make a voice AI agent recognize returning callers — recall their past calls and preferences by meaning and map their account relationships as a graph, so it greets them by context, not a cold script. For any STT/TTS stack (Vapi, LiveKit, Twilio, Pipecat).

All recipes· voice-agents· 15 minutesintermediateen
Instance: localhost:8080

Opens your running SynapCores (Caller Memory & Personalization (returning callers) will be staged for a preview — nothing runs until you click Run). No instance yet? Install free in ~30s.

Share

Objective

Nothing says "we don't know you" like a returning caller having to re-explain everything. A personalized voice agent recognizes the caller, recalls their recent issues and stated preferences by meaning, and understands how they relate to an account and product — so it opens with "Hi Dana, is this still about the export speed?" instead of a cold script. Here you'll build that on one database: a semantic caller-memory store plus a small relationship graph. Your STT/TTS and telephony live outside; the caller brain is the database. See Use it from your agent for wiring it into any voice stack.

Step 1: Create the caller-memory store

Per-caller memories — past issues, preferences, commitments — embedded for recall.

CREATE TABLE IF NOT EXISTS recipe_caller_memory (
  memory_id  INTEGER PRIMARY KEY,
  caller_id  TEXT,                                    -- stable id, e.g. phone hash or account
  content    TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  embedding  VECTOR(384)
);

Step 2: Seed memories for a returning caller

What the agent learned on prior calls with this caller.

INSERT INTO recipe_caller_memory (memory_id, caller_id, content) VALUES
 (1,'caller-7','Called last week about slow report exports; we promised a fix by Friday.'),
 (2,'caller-7','Prefers to be called Dana, not Ms. Cole.'),
 (3,'caller-7','Gets frustrated by long hold times; keep it efficient.'),
 (4,'caller-7','Is on the Pro plan and uses the analytics product daily.'),
 (5,'caller-9','Called about billing; resolved a double charge.');

Step 3: Embed the memories

The embedding model runs in-database, making every caller memory recallable by meaning.

UPDATE recipe_caller_memory SET embedding = EMBED(content);

Step 4: On answer, recall this caller's relevant history

The phone rings from a known caller; pull the memories that bear on a likely follow-up.

SELECT content,
       COSINE_SIMILARITY(embedding, EMBED('issue with exports being slow')) AS relevance
FROM recipe_caller_memory
WHERE caller_id = 'caller-7'
ORDER BY relevance DESC
LIMIT 2;

Step 5: Recall a stored preference to personalize tone

Retrieve how this caller likes to be addressed and handled — small touches that build trust. Project the similarity as relevance so we can order by it.

SELECT content,
       COSINE_SIMILARITY(embedding, EMBED('how does the caller prefer to be addressed and treated?')) AS relevance
FROM recipe_caller_memory
WHERE caller_id = 'caller-7'
ORDER BY relevance DESC
LIMIT 2;

Step 6: Map the caller's relationships (graph)

Model the caller's account and product so the agent has the right context for the conversation.

MERGE (c:Caller {id: 'caller-7', name: 'Dana'})
MERGE (a:Account {id: 'acct-3', tier: 'Pro'})
MERGE (p:Product {name: 'Analytics'})
MERGE (c)-[:HOLDS]->(a)
MERGE (a)-[:USES]->(p)
MERGE (c)-[:LAST_ISSUE]->(i:Issue {topic: 'export-speed', status: 'open'});

Step 7: Walk the graph for the caller's open issue and account

One query gives the agent the caller's tier, product, and unresolved issue to open the call.

MATCH (c:Caller {id: 'caller-7'})-[:HOLDS]->(a:Account)-[:USES]->(p:Product)
MATCH (c)-[:LAST_ISSUE]->(i:Issue)
RETURN c.name AS caller, a.tier AS tier, p.name AS product, i.topic AS open_issue, i.status AS status;

Step 8: Generate a personalized greeting

Combine the recalled preference + open issue into a warm, context-aware opener. Materialize the top recalled memory (projecting the score we order by), then generate from that row.

CREATE TABLE IF NOT EXISTS recipe_caller_top (memory_id INTEGER PRIMARY KEY, content TEXT, relevance DOUBLE);
INSERT INTO recipe_caller_top (memory_id, content, relevance)
SELECT memory_id, content,
       COSINE_SIMILARITY(embedding, EMBED('slow export issue we were fixing')) AS relevance
FROM recipe_caller_memory
WHERE caller_id = 'caller-7'
ORDER BY relevance DESC
LIMIT 1;
SELECT GENERATE(
  'Write a one-sentence warm phone greeting. Address the caller as Dana. ' ||
  'Reference this open issue from a prior call: ' || content ||
  '. Be efficient and friendly.') AS greeting
FROM recipe_caller_top;

Cleanup (Optional)

DROP TABLE IF EXISTS recipe_caller_memory;
DROP TABLE IF EXISTS recipe_caller_top;
MATCH (n:Caller) DETACH DELETE n;
MATCH (n:Account) DETACH DELETE n;
MATCH (n:Product) DETACH DELETE n;
MATCH (n:Issue) DETACH DELETE n;

Expected Outcomes

  • Step 4 recalls the open "slow export, fix promised by Friday" memory for the returning caller.
  • Step 5 retrieves "prefers to be called Dana" and "keep it efficient" — preferences that shape tone.
  • Step 7 returns Dana's Pro tier, Analytics product, and the open export-speed issue in one graph walk.
  • Step 8 generates a warm opener that uses her name and references the unresolved issue — recognition, not a cold script.

You now have a voice agent that recognizes returning callers, recalls their context by meaning, and knows their account relationships — personalization from the first second of the call.

Use it from your agent (framework-agnostic — the DB is the brain, the voice stack is swappable)

Caller personalization is just a memory store + a relationship graph, queried by caller_id from any voice runtime:

  • REST / SDKPOST /v1/query/execute (any language), or @synapcores/sdk client.executeQuery(...). On call-answer, look up the caller by phone/account, run the Step-4/5 recall and the Step-7 graph walk, and feed the Step-8 greeting to TTS. Works with Vapi, LiveKit Agents, Pipecat, Twilio, or Retell.
  • MCP (native, on by default) — point your voice runtime's MCP client at ws://<your-instance>/mcp?token=<jwt> (JWT from one POST /v1/auth/loginaccess_token). The query tool recalls caller memory; the execute tool runs Cypher for the relationship graph and writes new memories after the call — personalization as tool calls.
  • Any framework — the same caller brain powers a phone line, a returning-user web voice widget, or a chat channel. The database is the brain; the framework (and the voice stack) is swappable.

Key Concepts Learned

  • Keying memories by a stable caller_id gives cross-call recognition and continuity.
  • Semantic recall surfaces the relevant prior issue, not the whole call history.
  • A small relationship graph supplies account, product, and open-issue context in one walk.
  • Because it's plain data ops (SQL + Cypher / REST / MCP), caller personalization works with any STT/TTS stack — the database-as-the-brain pattern the voice cluster builds on.

Tags

voice-agentcaller-memorypersonalizationvectorembeddingsknowledge-graphmcp

Run this on your own machine

Install SynapCores Community Edition free, paste the SQL or Cypher above into the bundled web UI, and watch it run.

Download Free CE