Retell AI + ContextDB

Retell handles the call. ContextDB remembers the caller.

Load caller history for a Retell call, evaluate memory policy inside a custom function, enforce host identity and current state, and save sourced facts from the post-call webhook.

Point a Retell custom function at your backend. Have the backend call evaluate_action before any booking or account change, then call report_execution after each outcome. recall_for_action is a compatibility retrieval helper, not host authorization. Examples use the public Cloud client against ContextDB Cloud. The local engine uses pycontextdb. This independent guide has no vendor partnership or endorsement.

Backend setup

Install the Cloud client first.

The handlers below are adapter patterns, not a standalone Retell app. Connect the named authentication, identity, and calendar functions to your backend before running them.

shell
python -m venv .venv
source .venv/bin/activate
pip install contextdb-cloud-client==0.2.0a2 fastapi uvicorn
export CONTEXTDB_API_KEY="your-project-key"
export CONTEXTDB_BASE_URL="https://api.contextdb.ai"

During the call

Check the customer's confirmed details before booking.

Retell custom functions call your HTTPS endpoint mid-conversation and speak the result. Your endpoint authenticates the function request, derives the caller from the verified call, checks host permissions and the current slot, then branches on the durable ContextDB decision.

Custom function
# FastAPI endpoint behind a Retell custom function
# name: check_before_booking
from contextdb_cloud_client import CloudClient
from fastapi import HTTPException, Request

@app.post("/retell/check-before-booking")
async def check_before_booking(req: Request):
    function_call = await authenticate_retell_function(req)
    args = function_call.args
    caller = await identities.customer_for_call(function_call.call_id)
    slot = await calendar.get_slot(args["service"], args["day"])
    authorized = await calendar.can_book(
        actor_id=function_call.actor_id,
        customer_id=caller,
        slot=slot,
    )
    if not authorized:
        raise HTTPException(status_code=403, detail="not authorized")

    async with CloudClient(BASE_URL, api_key=KEY) as cdb:
        decision = await cdb.evaluate_action(
            caller, f"book {args['service']} on {args['day']}"
        )

        if decision.outcome == "act":
            try:
                booking = await calendar.book(
                    caller,
                    args["service"],
                    args["day"],
                    expected_version=slot.version,
                )
            except SlotChanged:
                await cdb.report_execution(
                    caller, decision.decision_id, "appointment.book", "failed",
                    idempotency_key=f"retell-receipt-{decision.decision_id}",
                    error_code="current_state_changed",
                )
                return {"result": "the slot changed, so nothing was booked"}
            await cdb.report_execution(
                caller, decision.decision_id, "appointment.book", "succeeded",
                idempotency_key=f"retell-receipt-{decision.decision_id}",
                external_ref=booking.ref,
            )
            return {"result": f"booked; reference {booking.ref}"}

        if decision.outcome == "ask":
            await cdb.report_execution(
                caller, decision.decision_id, "appointment.book", "skipped",
                idempotency_key=f"retell-receipt-{decision.decision_id}",
            )
            return {"result": "ask the caller to confirm the day"}

        if decision.outcome == "abstain":
            await cdb.report_execution(
                caller, decision.decision_id, "appointment.book", "skipped",
                idempotency_key=f"retell-receipt-{decision.decision_id}",
            )
            return {"result": "do not book; offer a human handoff"}

        raise RuntimeError("unknown ContextDB action outcome")

After the call

Save the important customer details after the call.

Retell posts call events to your webhook when the call ends. Store the durable fact with its source and keep the returned memory ID. The user_stated source records who said it. It does not set confirmed. Call confirm against that exact ID only after an authenticated actor explicitly attests.

Post-call webhook
@app.post("/retell/webhook")
async def on_call_ended(req: Request):
    event = await authenticate_retell_webhook(req)
    if event.event != "call_analyzed":
        return {"ok": True}

    caller = await identities.customer_for_call(event.call_id)
    instruction = require_user_quoted_instruction(event)
    async with CloudClient(BASE_URL, api_key=KEY) as cdb:
        # remembering the statement does not confirm it
        saved = await cdb.remember(
            caller,
            instruction.content,
            source="user_stated",
            confidence=0.6,
            action_relevant=True,
            idempotency_key=f"retell-{event.call_id}-instruction",
        )
    await confirmation_store.bind(event.call_id, caller, saved.id)
    return {"ok": True, "pending_memory_id": saved.id}

@app.post("/retell/confirm-memory")
async def confirm_memory(req: Request):
    attestation = await authenticate_retell_attestation(req)
    if not attestation.actor_said_yes:
        raise HTTPException(status_code=400, detail="explicit yes required")
    caller = await identities.customer_for_call(attestation.call_id)
    pending = await confirmation_store.get(attestation.call_id)
    if pending.customer_id != caller:
        raise HTTPException(status_code=403, detail="identity mismatch")
    async with CloudClient(BASE_URL, api_key=KEY) as cdb:
        await cdb.confirm(
            caller,
            pending.memory_id,
            idempotency_key=f"retell-confirm-{pending.memory_id}",
        )
    return {"ok": True, "confirmed_memory_id": pending.memory_id}

Retell call order

Add memory at four points in the Retell call.

Moment Retell surface ContextDB call
Call connects Inbound webhook / dynamic variables recall → caller snapshot into the prompt
Agent wants to book, refund, change Custom function evaluate_action → branch, then report_execution
Caller says an explicit yes Custom function confirm → record the authenticated attestation
Call ends Post-call webhook remember with source and confidence

Retell implementation

Where caller memory fits in a Retell call.

Does this add latency to the conversation?

Ordinary turns are untouched. The model talks as fast as Retell lets it. The gate runs only inside custom functions guarding consequential actions, where a beat of "let me check that for you" is natural and the alternative is booking the wrong day.

Should I store the Retell transcript in ContextDB?

No. Keep transcripts and recordings in Retell or your archive. Store the extracted, action-relevant facts with provenance, and keep the call ID in your own systems as the pointer back to the transcript.

How do I identify the caller across calls?

Use a stable identifier you control, such as your customer ID resolved from a verified Retell call and your own identity mapping, as the ContextDB user_id. Do not accept the partition from custom-function model arguments.

Build the Retell memory flow with the SDK.

Start with recall, then gate one custom function before it books.