What recall gives you back
This is the exact response shape POST /v1/recall_and_enrich returns, read from the route handler rather than an older document. Every field below is either always present or explicitly marked conditional — nothing here is aspirational.
The shape
The caller, memory, and enrichment values below are an illustrative example, not a real caller — the same convention used on API reference.
{
"trace_id": "b6b6c6d2-...",
"known": true,
"caller": {
"name": "Mike Reynolds",
"first_seen_ms": 1750000000000,
"last_seen_ms": 1751000000000,
"total_calls": 3,
"tags": ["fleet", "commercial"],
"custom_attributes": {}
},
"memory": {
"summary": "Repeat customer, 3 calls over 2 months. Fleet account.",
"recent_calls": [
{
"call_id": "int_...",
"started_at_ms": 1750900000000,
"duration_s": 92,
"summary": "Confirmed brake inspection for Thursday",
"outcome": "appointment_booked"
}
]
},
"enrichment": {
"person": { "name": "Mike Reynolds", "title": null, "company": null },
"company": null,
"phone": { "carrier": "Verizon", "line_type": "mobile" }
},
"timing_ms": { "total": "<measured>", "memory_ms": "<measured>", "enrichment_ms": "<measured>", "cache_hit": true },
"freshness": { "memory_age_ms": "<measured>", "enrichment_age_ms": null }
}
| Block | What it is |
|---|---|
trace_id | Correlation ID for this request. Log it; it's what support needs to find the request. |
known | Whether a caller record existed before this request — not whether enrichment succeeded. |
caller | Stored profile: name, first_seen_ms, last_seen_ms, total_calls, tags, custom_attributes. total_calls (and the other fields) can be null — that means "unavailable for this request," never "zero" or "none." See API reference for the branching rule. |
memory.summary | A short rolling summary. Empty string for a brand-new caller. |
memory.recent_calls | Recency-ordered call history — most recently updated first, not ranked by relevance to the current call. Your application decides what's worth surfacing. |
enrichment | person, company, phone — each independently nullable. A null block means that source didn't complete inside the request deadline, not that the caller has no such data. |
timing_ms | total, memory_ms, enrichment_ms, cache_hit for this request. A measurement, not a published benchmark — public latency language stays designed for sub-300ms voice recall. |
freshness | memory_age_ms, enrichment_age_ms — how old the served data was when it was served, or null when that isn't known. |
degraded | Present and true only when an authenticated caller-memory read failed and the route returned the safe empty-shape fallback instead of erroring. Absent on a normal response. |
The facts field — and what production actually serves today
The response can carry one more field: memory.facts, an array of governed fact strings pulled from the same substrate documented in Locked facts. It is additive and off by default — the handler only builds it, and only adds the key to the response at all, when the tenant's environment has ENABLE_MEMORY_UNIFICATION_READ set to "1". When the flag is off, memory has exactly the two fields above — no facts key, not even null — so existing integrations parse the same response shape either way.
When the flag is on, facts has three states and they mean different things:
| Value | Meaning |
|---|---|
| absent | The flag is off. The governed read was never attempted. |
[] | The read ran, and this caller has no governed facts. A measurement. |
null | The read did not complete — it exceeded its internal budget, or errored. Unavailable, not empty. |
null is deliberate, and it is the one to branch on. The governed read is bounded so that a slow read can never delay the voice response, which means it can be dropped while the rest of the packet is served normally under a 200. If a dropped read reported [], a caller whose history we hold would be indistinguishable from a caller we have never seen. So the packet says null instead. Do not coalesce it — facts ?? [] throws away exactly the distinction the field exists to carry.
Verified against the deployed configuration: wrangler.jsonc's single vars block does not set ENABLE_MEMORY_UNIFICATION_READ at all. Production does not serve memory.facts today. Treat any description of facts you see elsewhere as the additive design, not the current default response — this page describes what a request against https://mcp.mnemix.ai actually returns right now.
Put this in a prompt without wrecking the agent
- Don't dump the JSON. Select the two or three fields that change what the agent should say — usually
caller.name,memory.summaryor the top ofmemory.recent_calls, and one enrichment field — and render them as short natural-language lines, the way the Vapi and Bland kits already do with prompt variables. - Never block the greeting on enrichment.
enrichmentfields are independently nullable by design; a cold caller with all threenullis a normal response, not a failure. Give the agent a useful fallback line for that case. - Don't treat recency as relevance.
recent_callsis ordered by recency, not by what matters to today's call — filter or pick in your application before it reaches the prompt. - Branch
nullcorrectly.caller.total_calls: nullmeans "unknown," not zero — a prompt that says "this is your first call" on anullcount asserts something you don't know.memory.facts: nullis the same rule: it means the governed read did not complete, so say nothing about what you do or don't remember rather than implying you have no history. - Treat
degraded: trueas a signal to soften, not hide. The shape is still safe to render; just don't present a degraded read as complete history.
Next steps
- API reference — the full request/response contract for all three routes.
- Hangup, and what happens next — what changes in this same response after a call ends.
- What isn't built yet — the honest list of what this substrate does not do today.