# Bolna

> Give a Bolna voice agent a memory of each caller, from your own server. What matters about the caller goes into the agent's prompt before a call starts, on calls in and out, and each call is saved when it ends.

Give a Bolna voice agent a memory of each caller. Before Bolna answers an incoming call, it can ask your
server about the caller; when you place a call yourself, you send what to know with it; and when a call ends,
Bolna sends you its transcript. Answer the first two with what matters about the caller, and save the third.
The next call starts knowing what was said in this one.

## Install

```bash
pip install fastapi uvicorn httpx geniffy
```

```bash
uv add fastapi uvicorn httpx geniffy
```

Set `GENIFFY_API_KEY` from **API keys** in the Geniffy app, `BOLNA_API_KEY` and `BOLNA_AGENT_ID` from Bolna,
and `BOLNA_TOKEN` to a long random secret of your own. Bolna sends it with every request, so your server can
tell Bolna's requests from anyone else's.

## Your server

```python
import hmac
import os

import httpx
from fastapi import FastAPI, HTTPException, Request
from geniffy import AsyncGeniffy

geniffy = AsyncGeniffy()                          # reads GENIFFY_API_KEY
bolna = httpx.AsyncClient(base_url="https://api.bolna.ai",
                          headers={"Authorization": f"Bearer {os.environ['BOLNA_API_KEY']}"})
TOKEN = os.environ["BOLNA_TOKEN"]
app = FastAPI()


def space_for(number: str | None) -> str:
    """The caller, by the digits of their number. If you know callers as customers, use your customer id, and
    the same memory serves their calls, chats and emails."""
    digits = "".join(ch for ch in number or "" if ch.isdigit())
    if not digits:                                # never fall back to one shared memory for unknown callers
        raise HTTPException(400, "This call has no caller number.")
    return f"user_{digits}"


def check(request: Request) -> None:
    """Bolna sends your token as a bearer token."""
    given = request.headers.get("Authorization", "").removeprefix("Bearer ")
    if not hmac.compare_digest(given.encode(), TOKEN.encode()):
        raise HTTPException(401)


async def known_about(number: str | None) -> str:
    brief = await geniffy.space(space_for(number)).brief(limit=12)
    return "\n".join(f"- {m['text']}" for m in brief["memories"]) or (
        "Nothing is known about this caller yet. If they mention an earlier call, say so rather than guessing.")


@app.get("/bolna/caller")
async def caller(request: Request, contact_number: str = ""):
    """Bolna asks this before it answers an incoming call; what it returns fills {{memory}} in the prompt."""
    check(request)
    return {"memory": await known_about(contact_number)}


async def place_call(number: str) -> str:
    """A call out that starts knowing the person it calls."""
    r = await bolna.post("/call", json={"agent_id": os.environ["BOLNA_AGENT_ID"], "recipient_phone_number": number,
                                        "user_data": {"memory": await known_about(number)}})
    r.raise_for_status()
    return r.json()["execution_id"]


@app.post("/bolna/calls")
async def calls(request: Request):
    """Each update on a call. The completed one carries the transcript; Bolna can send it more than once, so
    it is saved under the call's own id, and a call sent again is the same source."""
    check(request)
    call = await request.json()
    if call.get("status") != "completed":
        return {}
    turns: list[dict] = []
    for line in (call.get("transcript") or "").splitlines():
        role, colon, said = line.partition(":")
        if colon and role.strip() in ("assistant", "user"):
            turns.append({"role": role.strip(), "content": said.strip()})
        elif turns and line.strip():              # a line that carries on what was being said
            turns[-1]["content"] += " " + line.strip()
    turns = [t for t in turns if t["content"]]
    if turns:                                     # user_number: the caller, or the person called
        await geniffy.space(space_for(call.get("user_number"))).memories.add(
            messages=turns, title="Call", external_id=f"bolna-{call['id']}")
    return {}
```

Run it with `uvicorn server:app`, somewhere Bolna can reach over HTTPS, and call `place_call(number)` from
your own code to ring someone.

Bolna waits at most three seconds for an answer about a caller, so the agent is given `brief()`: the caller's
most load-bearing memories, newest first, read without a model call. A call is saved once it is `completed`,
under its own id, so when Bolna sends the same call again, nothing is learned twice.

## In Bolna

In your agent's prompt, put the memory where you want it:

```text
You are a friendly phone assistant. Keep answers short.

<memory>
{{memory}}
</memory>
```

1. On the agent's **Inbound** tab, match callers through your own API: the endpoint is
   `https://your-server/bolna/caller`, and the auth token is your `BOLNA_TOKEN`.
2. On the agent's **Extractions** tab, set the **Webhook URL** to `https://your-server/bolna/calls`. Turn on
   **Add headers** and add `Authorization` with the value `Bearer ` followed by your `BOLNA_TOKEN`.

When nothing is known about a caller, the memory says so in one sentence, so the agent says it doesn't know
instead of guessing. Each call is added as one conversation titled `Call`. Geniffy keeps who said what, so what
the caller said becomes a fact about them, and what your agent said stays the agent's.

Source: https://docs.geniffy.com/integrations/bolna
