Your setup
voice AI guide
Step 5 of 7
Step 05 · The ledger

Add memory.

A small SQL log gives your agent rolling context across calls. So it remembers the dishwasher service centre, the Friday hostess, the dentist who never sees you on time. Optional but recommended. Ten minutes.

Without memory, every call is the first call. With memory, your agent gets smarter every week.

You will create a Cloudflare D1 database (free tier: 5 million reads, 100,000 writes per day, far more than personal use), connect it to your Worker, and update the Worker code to log every call.

Browser route · Cloudflare dashboard

Create the database

  1. Open Chrome. Go to dash.cloudflare.com. Sign in.
  2. In the left sidebar, click Workers & Pages, then D1.
  3. Click Create database.
  4. Name it voice-bot-memory. Pick a location close to you. Click Create.

Add the table

  1. On the new database page, click the Console tab.
  2. Paste this SQL into the console:
CREATE TABLE calls (
  id TEXT PRIMARY KEY,
  to_number TEXT,
  objective TEXT,
  summary TEXT,
  created_at INTEGER
);
  1. Click Execute. You should see "Success" or similar.

Bind the database to your Worker

  1. In the left sidebar, click Workers & Pages, then click your voice-bot Worker.
  2. Click Settings, then Bindings.
  3. Click Add, then pick D1 database.
  4. Variable name: DB. Database: pick voice-bot-memory.
  5. Click Save. The Worker will redeploy automatically.
Terminal route · wrangler CLI

From your voice-bot project directory:

wrangler d1 create voice-bot-memory

Wrangler outputs a database ID. Add it to wrangler.toml at the bottom:

[[d1_databases]]
binding = "DB"
database_name = "voice-bot-memory"
database_id = "<the-id-wrangler-printed>"

Create the table:

wrangler d1 execute voice-bot-memory --command \
  "CREATE TABLE calls (id TEXT PRIMARY KEY, to_number TEXT, objective TEXT, summary TEXT, created_at INTEGER);"

Re-deploy so the binding takes effect:

wrangler deploy

Wire memory into the Worker

The Worker now has a database connection. We need to make it actually use the database. Two changes: log each call when it starts, and look up prior calls to the same number to give your agent context.

Open your Worker code (browser editor or src/index.js) and replace it with the version below.

export default {
  async fetch(request, env) {
    if (request.method !== "POST") return new Response("OK");

    const form = await request.formData();
    const message = (form.get("Body") || "").toString();
    const from = (form.get("From") || "").toString();
    if (!message || !from) return twiml("");

    const match = message.match(/call\s+(\+\d+)\s+about\s+(.+)/i);
    if (!match) return twiml("Format: call +1xxx about <objective>");
    const [, toNumber, objective] = match;

    // Look up prior context for this number
    let context = "";
    if (env.DB) {
      const prior = await env.DB.prepare(
        "SELECT summary FROM calls WHERE to_number = ? ORDER BY created_at DESC LIMIT 1"
      ).bind(toNumber).first();
      if (prior && prior.summary) {
        context = "Prior call note: " + prior.summary;
      }
    }

    // Trigger the EL outbound call
    const callRes = await fetch(
      "https://api.elevenlabs.io/v1/convai/twilio/outbound-call",
      {
        method: "POST",
        headers: {
          "xi-api-key": env.ELEVENLABS_API_KEY,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          agent_id: env.ELEVENLABS_AGENT_ID,
          agent_phone_number_id: env.ELEVENLABS_AGENT_PHONE_ID,
          to_number: toNumber,
          conversation_initiation_client_data: {
            dynamic_variables: {
              objective,
              context,
              your_name: env.YOUR_NAME,
              agent_name: env.AGENT_NAME,
            },
          },
        }),
      }
    );
    const data = await callRes.json();

    // Log the call as a row
    if (env.DB && data.conversation_id) {
      await env.DB.prepare(
        "INSERT INTO calls (id, to_number, objective, summary, created_at) VALUES (?, ?, ?, ?, ?)"
      ).bind(data.conversation_id, toNumber, objective, "", Date.now()).run();
    }

    return twiml(`${env.AGENT_NAME} is calling ${toNumber} now.`);
  },
};

function twiml(reply) {
  const body = `<Response>${reply ? `<Message>${escape(reply)}</Message>` : ""}</Response>`;
  return new Response(body, { headers: { "Content-Type": "text/xml" } });
}

function escape(s) {
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

Save and re-deploy. (Browser route: click Save and Deploy in the editor. Terminal route: wrangler deploy.)

Test it

Send the same WhatsApp message you sent in step 4. The Worker now logs the call. After it finishes, you can confirm the row was written.

  1. In Chrome, go to dash.cloudflare.com, then Workers & Pages, then D1, then voice-bot-memory.
  2. Click Console. Run: SELECT * FROM calls;
  3. You should see one row with your test call's data.

The next time your agent calls the same number, it will get the prior summary in its context. Once the post-call summary feature is wired (step 7), this loop closes fully.