Guides / Use cases

Voice agents

For phone lines and voice assistants: an agent that knows who is calling and what happened last time, without adding silence to the call. You read the caller's memory while the call connects, store each utterance in one fast request that runs next to your model, and leave anything that takes seconds until after the hang-up.

What memory adds

Without memory, every call starts with "how can I help?" and the same questions. With it, the agent knows the story so far:

Without memoryWith Recall
Monday's callThe caller reports that the internet drops out every evening. The agent resets the line and promises to check it again on Friday.
Friday, same caller, verified"What is the reason for your call today?""Is this about the evening drop-outs? We said we would check your line today."
A month later"Which router do you have?""Is it still the white router we sent in August? And shall I call you back after six, like last time?"

Recall does not hear or speak. Your speech-to-text, model and text-to-speech do; Recall stores the transcript and gives your model the caller's memory.

How to model it

On your lineIn RecallWhy
Your phone line or voice productWorkspace, e.g. support-lineOne per product and environment, so test calls never mix with real callers.
The callerPeer: your account id (cust-1042) once verified, otherwise caller- and a keyed hash of the numberOne stable id per person, and never the raw number.
Your voice agentPeer voice-agent, with observeMe: falseRecall does not learn from the agent's lines, only reads them as context, so they cost less.
A callSession, e.g. call-7f3e9a2c, from your telephony provider's call idOne transcript per call, its own running summary, and one place to delete it.
What the caller said on earlier callsThe peer card and facts, returned with the contextReady for the first prompt, before the caller says a word.

Why not the phone number? Ids show up in request paths, logs, exports and the dashboard, and cannot be renamed. A plain SHA-256 of a number protects nothing, because every possible number can be hashed and compared. A keyed hash (an HMAC with a secret only your server holds) cannot be reversed without the key, and the same number always gives the same id, so you can still find a caller to delete them. A number is still not a person, though: numbers change hands and a household can share one line. Once a caller is verified against your accounts, key memory by your account id and keep the number-to-account mapping in your own system; use the hashed number only for callers you cannot identify.

Build it

Install the SDK (npm install @recall-memory/sdk or pip install recall-mem) and set RECALL_API_KEY on the server that handles your call events. Configure the agent once at startup; per call you only need two handles, which make no request.

import { createHmac } from "node:crypto";
import { Recall } from "@recall-memory/sdk";

const recall = new Recall({ workspaceId: "support-line" });

// Ids show up in URLs, logs and exports, so never use the raw number. A plain hash of a phone
// number is easy to reverse; a keyed hash is not, without the key from your secret store.
const secret = process.env.CALLER_ID_SECRET ?? "local-dev-only";
const callerId = (phone: string) =>
  `caller-${createHmac("sha256", secret).update(phone).digest("hex").slice(0, 32)}`;

// Once, at startup: Recall reads the agent's lines but learns nothing from them, and keeps a
// running summary of each call every 10 messages (the default is 20).
const agent = recall.peer("voice-agent", { configuration: { observeMe: false } });
await recall.setConfiguration({ summary: { messagesPerShortSummary: 10 } });

// Per call: the caller, and a session named after your telephony provider's call id.
// Handles make no request; the first utterance creates the session.
const caller = recall.peer(callerId("+447700900123")); // or your account id, once verified
const call = recall.session("call-7f3e9a2c");

Before the first word: read the caller's memory. As soon as the call connects, while your greeting plays, read the session context with the caller as peerTarget. It is one request (a context call measured 0.3 to 0.7 seconds) and works before the session exists, so a first-time caller gets empty context rather than an error. The result holds the caller's peer card and facts, ready as system messages. Until the caller is verified, keep personal details from it out of what the agent says.

// The call is connecting: read what Recall knows about the caller while your greeting plays,
// so the first reply needs no lookup. One request; a new caller just has no memory yet.
const start = await call.context({ peerTarget: caller, tokens: 1500 });
const system = start.toOpenAI(agent); // peer card and known facts, as system messages

Each utterance: one request, off the critical path. When speech-to-text gives you the final transcript of an utterance, send it with turn, together with the agent's previous line, so one request stores both. A turn measured 0.3 to 0.7 seconds, which is too long to add to every reply. Your model does not need to wait for it: it already has the memory from the start of the call and the call so far, so start the model call and the turn together. Use the context the turn returns for the next utterance; on a long call it replaces older turns with a summary, so the prompt stays within tokens.

// Each finished utterance (the final transcript from speech-to-text): one request that stores
// it with the agent's previous line and returns fresh context. Start it next to your model call,
// not before it: the model already has `system` and the call so far.
const greeting = "Thanks for calling. How can I help?";
const heard = "Hi, it's me again. The internet keeps dropping out in the evenings.";
const stored = call.turn([agent.message(greeting), caller.message(heard)], {
  peerTarget: caller,
  tokens: 1500,
});
// Your voice model replies now, from `system`, the call so far and `heard`.
const reply = "Sorry about that. Is it the router we replaced in August?";
const { context } = await stored; // for the next utterance; older turns become a summary

Questions wait until the call is over. chat took 2 to 4.5 seconds at minimal or low, which is dead air on a phone line. Never call it inside a turn. Ask before the call when you can (an outbound call you are about to place), or after it, for your CRM or to prepare the next call. If a question must inform this call, start it in the background when the call connects and use the answer only if it arrives in time.

After the hang-up: the summary and anything slow. Store the agent's last line. Recall keeps a running summary of each session, written every 20 messages by default (at least 10, set with messagesPerShortSummary), and summaries() returns it; a short call may not have one. A queue.empty webhook tells your server when learning for the call has finished, so you do not need to poll. For a note on every call, short ones included, ask a question as below ($0.005 at low), or have your own model write it from the transcript you already hold.

// Hang-up: store the agent's last line. Anything slow happens now, off the call.
await call.addMessages([agent.message(reply)]);
// (waitForIdle is for this demo; in production the queue.empty webhook says when to read.)
await recall.waitForIdle();
// Recall's running summary of the call (every 10 messages, as set at startup); a call as
// short as this one has none yet.
const { shortSummary } = await call.summaries();
// A question for your CRM or the next call: one model call of a few seconds, fine now.
const followUp = await caller.chat("What have we promised this caller, and by when?", {
  reasoningLevel: "low",
});

A caller asks to be forgotten. Verify them, derive the id from their number with the same key, delete their calls (the agent's lines quote them), then the caller. delete() erases their messages, every fact and card about them, and cached answers.

// A caller asks to be forgotten: derive the same id from their number, delete their calls
// (the agent's lines quote them), then the caller.
const person = recall.peer(callerId("+447700900123"));
for (const s of await (await person.sessions()).toArray()) await s.delete();
const removed = await person.delete();

Other languages and voice platforms

Recall has no telephony SDK; call it from whatever handles your call events. Over REST, the call start and each utterance are one request each. The context read:

curl -G $RECALL_URL/v3/workspaces/support-line/sessions/call-7f3e9a2c/context \
  -d tokens=1500 -d peer_target=cust-1042 \
  -H "Authorization: Bearer $RECALL_API_KEY"

Each utterance, with the agent's previous line. The Idempotency-Key header makes a retry safe; the speech-to-text segment id is a good value:

curl $RECALL_URL/v3/workspaces/support-line/sessions/call-7f3e9a2c/turn \
  -H "Authorization: Bearer $RECALL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: call-7f3e9a2c-utt-7" \
  -d '{"messages":[
        {"peer_id":"voice-agent","content":"Thanks for calling. How can I help?"},
        {"peer_id":"cust-1042","content":"Hi, me again. The internet keeps dropping out."}
      ],
      "context":{"tokens":1500,"peer_target":"cust-1042"}}'

The answer is {"messages": [...], "context": {...}}: the stored messages and the context for the next prompt. The first turn creates the session and the caller. See the REST API.

Settings that matter

SettingUse
tokensThe context goes into every prompt, so keep the budget small: a shorter prompt is faster for your model. Older turns become a summary.
observeMe: false on the agentRecall learns about the caller only, and the agent's lines cost less.
summary.messagesPerShortSummary (workspace or session)How often the running summary of a call is written: every 20 messages by default, at least 10.
idempotencyKey on turn (idempotency_key in Python)The SDKs add one per call, so their own retries never store an utterance twice. Pass your own, such as the speech-to-text segment id, if another worker may send it again.
timeout and maxRetries (max_retries in Python)On a live call, give up early and carry on without memory rather than leave the caller in silence. TypeScript also takes timeout per request.
reasoningLevel: "low"For questions after the call. See Reasoning levels and models.

Cost and speed

Measured from Europe against the hosted API (Frankfurt), October 2026: a turn or context call took 0.3 to 0.7 seconds, a question at minimal or low 2 to 4.5 seconds. Learning happens in the background and never holds up a call. Speech-to-text, your model and text-to-speech already sit between the caller and the reply, so keep Recall beside that path, not on it: context while the call connects, each turn next to your model, questions after the hang-up.

Context reads and turns are included; you pay for learning from the messages a turn stores, $1.75 per million message tokens with every message counting as at least 50, and for questions. A caller who calls four times a month, with 30 short utterances and 30 agent lines of about 30 tokens per call, and one question after each call:

ItemPer month
Caller utterances: 4 × 30 × 50 tokens × $1.75 / 1M$0.0105
Agent lines, read but not learned from: 4 × 30 × 30 tokens × $1.75 / 1M$0.0063
Questions after the call at low: 4 × $0.005$0.0200
Totalabout $0.04 per caller a month

More in Cost and speed.

Privacy and forgetting

This is practical guidance, not legal advice. See Privacy, consent and forgetting for the general picture.

Recall's own terms are in the privacy policy and the DPA.

Pitfalls

Next: Cost and speed · Customer support