Documentation

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 }
}
BlockWhat it is
trace_idCorrelation ID for this request. Log it; it's what support needs to find the request.
knownWhether a caller record existed before this request — not whether enrichment succeeded.
callerStored 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.summaryA short rolling summary. Empty string for a brand-new caller.
memory.recent_callsRecency-ordered call history — most recently updated first, not ranked by relevance to the current call. Your application decides what's worth surfacing.
enrichmentperson, 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_mstotal, 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.
freshnessmemory_age_ms, enrichment_age_ms — how old the served data was when it was served, or null when that isn't known.
degradedPresent 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:

ValueMeaning
absentThe flag is off. The governed read was never attempted.
[]The read ran, and this caller has no governed facts. A measurement.
nullThe 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.summary or the top of memory.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. enrichment fields are independently nullable by design; a cold caller with all three null is a normal response, not a failure. Give the agent a useful fallback line for that case.
  • Don't treat recency as relevance. recent_calls is ordered by recency, not by what matters to today's call — filter or pick in your application before it reaches the prompt.
  • Branch null correctly. caller.total_calls: null means "unknown," not zero — a prompt that says "this is your first call" on a null count asserts something you don't know. memory.facts: null is 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: true as 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