Cross-channel agent memory plane
Agent memory is a per-contact store of facts, preferences, goals, and conversation summaries that your agents learn and recall. The memory plane is the layer that makes that store usable when the customer shows up on any channel — voice, SMS, WhatsApp, or chat — instead of only re-reading the current conversation thread. Two services sit under the plane:- Resolution — at the start of a turn, the plane maps whatever identifier the channel carries (E.164 phone, contact id, unified identity id) to a single resolved scope: the contact plus their unified identity.
- Retrieval — the resolved scope builds a tenant-scoped vector filter; candidate items are deduplicated and recency-ranked before the top-K are rendered into the agent’s context.
Identity resolution flow
Each channel hands a different identifier to the runtime, so the plane resolves in up to three steps:- E.164 phone only (voice turns) — resolve
phone → contact_idthrough the registered lookup. When nothing matches, the turn proceeds with whatever scope was already supplied. - Contact id present (SMS / WhatsApp / chat) — resolve
contact_id → unified_identity_idso a contact merge can’t strand the turn on the pre-merge id (merges re-point memory in the same transaction). - Caller already supplied a unified identity — that short-circuits both lookups.
Retrieval: filter assembly, dedupe, recency
Retrieval runs against the tenant-scoped vector store that backs all agent memory, filtered and ranked in this order:- Tenant scope first. The filter asserts the tenant id before any contact-level clause; the platform’s strict-tenancy check rejects a filter that lacks it, and malformed tenant or contact shapes abort retrieval with an empty result rather than crossing a tenant boundary.
- Contact + unified identity. When a resolved identity is present, the filter matches a point by either
contact_idorunified_identity_id— so memory re-pointed by a mid-conversation merge stays retrievable, and pre-merge legacy points (which only carrycontact_id) are still matched. - Narrowing. Optional
agent_idandunified_conversation_idclauses restrict retrieval to one agent’s or one conversation’s notes. - Over-fetch and dedupe. The query fans out for roughly
limit × 3candidates, then drops content-identical items (normalised on trim/lower/first-160-chars) so a fact can’t double-occupy the top-K. - Recency decay + rerank. Scores decay ~5 % per week of age (floored so old items don’t vanish), and an optional reranker re-orders the pool before the top-K are returned.
Rendering memory into the turn
When retrieval produced items, the plane renders them as a single system-level block and hands it back to the agent executor. The block is prefixed with the constant marker- <item> line per memory. The executor uses that marker the same way it uses its RAG-context prefix: on every new turn it strips the previously attached memory block before appending the fresh one, so memory never accumulates across a conversation even when the conversation state is persisted between turns.
When resolution can’t identify a contact, or retrieval produced no items, no block is attached on that turn — the agent simply runs with the context it already has.
Reading memory through the CDP Profile API
Four endpoints expose a contact’s plane to your systems, one per identifier family that the Profile API already resolves. All four return the same shape, with PII masked by default, rate-limited at 60 requests per minute per tenant.By user id
GET /api/v1/cdp/profiles/by-user-id/:user_id/memoryBy email
GET /api/v1/cdp/profiles/by-email/:email/memoryBy phone
GET /api/v1/cdp/profiles/by-phone/:phone/memoryBy anonymous id
GET /api/v1/cdp/profiles/by-anonymous-id/:anonymous_id/memoryowner / admin / developer role with the contacts:read scope. Query params:
memory_type is one of fact, preference, goal, summary. When the contact’s GDPR opt-out flag is set (memory_enabled=false), the response is an explicit empty shape — never a leak of the opted-out plane:
PROFILE_NOT_FOUND. Malformed identifiers return a 422 validation error per the Profile API conventions.
Role-gated PII reveal
Itemcontent is PII-masked by default. Passing ?reveal=true unmasks it only when the caller has owner, admin, or developer role; every reveal writes an audit-log entry so operators can see who unmasked what.
- 401 — the request didn’t authenticate (missing/expired JWT).
- 403 — an authenticated caller without one of the three roles or without
contacts:read. Reveal is silently ignored for them: the route returns successfully with the content still masked rather than leaking or hard-failing.
The pre-turn identity bridge (internal)
The agent runtime needs to lift a caller’s phone into a contact before the executor turn runs, so it queries one internal endpoint and hands the result to the plane.phone; unmatched phones return 200 with null fields so the runtime proceeds in contact-only retrieval — 404 is deliberately never used for an unmatched row. contact_id-only turns resolve the unified identity. include_memory_enabled=true also returns the contact’s opt-out flag so the runtime can log the gate before retrieval. Server-to-server only: the internal token gate rejects external callers.
Browsing memory in the dashboard
Open AI Agents → Customer Memory (/agents/memory). Pick a contact, and the page runs the same plane surface as the CDP endpoints — you can read items, add a fact manually, and delete items. When a contact has the GDPR opt-out flag set, the dashboard shows the opted-out state explicitly rather than listing items.
GDPR consent and erasure
The plane honours the per-contactmemory_enabled flag (settable from the contact’s record or the consent surfaces) before any retrieval fan-out. The gate is consulted on every read and write:
- An opted-out contact returns an empty plane from the CDP endpoints, the dashboard shows the opt-out state, and the executor attaches no memory block.
- A contact-level erasure (
DELETE /api/v1/contacts/:contact_id/memory) purges every item the tenant holds on the contact. - An organisation-level offboard purges every item for the tenant.
Example: fetch plus reveal behaviour
Fetch a contact’s memory by email, then unmask when you’re an owner:Next steps
- Cost controls — memory retrieval adds embedding spend; budgets still apply.
- Tenant isolation — the tenant-first filter contract the plane enforces.
- Consent management — how
memory_enabledmaps to consent states and erasure. - AI Agents — the runtime that attaches the plane’s context block per turn.