kelam.sh Docs Try it Add to Claude
How it works

What's happening underneath

You do not need this page to use Kelam — describing what you want is the supported path, and everything below gets written for you. It's here for when you want to know what the thing you asked for actually became.

An agent

An agent is a folder of plain files. Four kinds, all optional except the first:

agent.yamlIts voice, its languages, which models run the call, and which built-in capabilities it's allowed to use. Full field list →
scenarios/*.mdWhat it does and how it behaves, written in English. Every file is concatenated into the system prompt, in filename order.
tools/*.pyThings it can actually do mid-call — check a calendar, look up an order, hit your API. Ordinary Python functions.
tests/*.yamlAssertions you can run before shipping: "greets and stays on topic", "never invents an appointment".

The Builder writes these for you; kelam pull puts them on your disk. Same files either way — an agent built in the browser can be pulled, edited in an editor, and pushed back.

A workspace and its number

Your workspace is your account: your agents, your calls, your secrets, your people. It comes with one phone number, shared by every agent on it. The first agent that wants a phone claims it and answers incoming calls; every agent after that uses the same number to dial out. Nothing to buy, nothing to point at anything.

Two escape hatches. An agent that never needs a phone at all (kelam create x --no-number) lives only in the browser and costs nothing to run. And an agent that needs a line of its own can buy a dedicated number (--buy-number) instead of sharing — a paid option.

Several agents on one number is the intended shape, not a workaround: one front desk answers, and hands the live caller to whichever colleague should take it. See the switchboard example.

Scenarios — the instructions

A scenario is a markdown file describing the agent's job. Write it the way you'd brief a new hire: who calls, what they want, what a good outcome looks like, what to do when it goes sideways.

# scenarios/01-instructions.md

You are the front desk for Brightside Dental. You answer the main line.

- Greet warmly, then find out what they need in one question.
- To book: get the patient's name, the reason, and a day. Offer the two
  nearest open slots. Read the booking back before you confirm it.
- If they describe severe pain or bleeding, treat it as urgent and offer
  to transfer them to the on-call dentist.
- Never quote a price. Say the office will confirm costs on arrival.

Rules of thumb: short sentences, concrete outcomes, and say what to do when it fails. Don't write placeholder brackets like [patient name] — the agent will read them aloud on a live call. Facts that change (your hours, a booking link) belong in context, not in the prompt.

Tools — what it can do besides talk

Two kinds. Built-in capabilities are one line each in agent.yaml: listing it is what turns it on, there's no second switch.

end_callHang up deliberately when the conversation is done.
transferSend the caller to a real phone number, off-platform.
handoffHand the live call to another one of your agents — the caller never leaves the line. With any_agent it can route to any colleague.
owner_callbackDial the account owner into the call as a third voice.
dtmfPress keypad digits — how it gets through "press 2 for billing". On by default.
holdTake a "can you hold a second" gracefully instead of talking into the void.
fetch_contextRe-read the stored facts mid-call, so it sees anything written since the call started.
background_noiseChange the ambient room behind its voice mid-call.
ask_operatorHand a piece of back-office work to your workspace's operator during the call.

Every agent also gets, for free, the ability to read its own past: its recent text threads and its previous calls with the person on the line.

Your own tools are Python files. A function with the @tool decorator becomes something the agent can call in conversation; its docstring is what the model reads to decide when to call it.

# tools/check_calendar.py
from kelam import tool, secret, context
import httpx

@tool
async def open_slots(day: str) -> str:
    """Open appointment slots for a given day, e.g. 'Saturday'."""
    r = await httpx.AsyncClient().get(
        context("BOOKING_API") + "/slots",
        params={"day": day},
        headers={"authorization": "Bearer " + secret("BOOKING_KEY")},
    )
    return r.text

Keep them fast — a tool call happens inside a conversational turn, so anything slow reads as dead air. The tool contract in full →

Context and secrets — what it already knows

Context is facts an agent has before the phone rings, so it never has to ask for something you already know. Three lanes, narrowing:

workspaceTrue for every agent you own: your business hours, your address, your tone.
agentTrue for one agent: which price list it quotes, which calendar it books.
callerTrue about one person: their name, their last order, what they asked for in March.

All three are fetched and seeded into the agent's head before every call connects. The caller lane is normally filled in automatically after calls, which is why an agent that has spoken to someone before opens the second call already knowing who they are. You can edit any lane by hand — in the Builder's Memory tab, or with kelam context — and a one-off fact for a single call rides along with -C key=value.

Secrets are the same idea for things that must stay hidden: API keys, tokens. They are stored encrypted, never printed back, and resolve only inside tool code via secret("NAME"). Set one with kelam secret-set; the value never appears on your command line.

A call

Four kinds, one record type:

Whatever the kind, when it ends you get: a recording, a word-for-word transcript, a one-line summary, who hung up and why, whether a real human actually answered, how long it took, how many turns it ran, and exactly what it cost.

You can follow a call while it happens — the transcript streams — and you can listen in silently, invisible to both sides. And you can end one at any moment.

Extraction — answers as data

Most calls are placed to find something out. Instead of reading transcripts, ask for typed fields up front:

$ kelam call supplier-bot +12065550123 \
    -e "price:number:the quoted price per pallet in USD" \
    -e "in_stock:boolean:whether they have it in stock" \
    -e "lead_time:string:how long delivery takes"

The agent is steered to find each one out naturally — it never announces that it's collecting fields — and after the call a pass over the transcript lands the values on the call record. Twenty calls become a table. You can also run extraction over calls you already made, with a field you only thought of afterwards.

Versions and deploys

Every save is a new version. Nothing is edited in place. Deploying is what makes a version the one that answers the phone; older versions stay callable, which is what makes the next section possible.

Practically: push creates the version, deploy makes it live. In the Builder both happen when you accept a change. Every call records the exact version that took it, so "which config said that?" is always answerable — and you can replay a bad call against that exact version.

Tests and experiments

Two ways to know a change helped.

Tests live in tests/*.yaml and run against the deployed agent. A turns test is text-only and free — it feeds the agent lines and asserts on how it replies and which tools it fires, including the ones that read history. A voice test is a real agent-to-agent call with a scripted caller persona, which costs nothing because no phone network is involved. Test calls are hidden from your stats.

Experiments compare variants of one agent on real traffic — a different voice, a cheaper model, a rewritten prompt — split by weight, sticky per caller, scored on a metric you choose (completion, cost, latency, a human thumbs-up, or one of your extracted fields). It won't declare a winner before it has enough calls to mean anything.

What it costs

Cents a minute, billed to the second: a small connect fee plus per-minute talk time, capped per call. A call nobody answers is free. Browser calls and agent-to-agent calls have no telephony cost at all, which is why the Builder encourages you to test that way.

Every call stores its own exact provider usage and a cost breakdown, so kelam costs and the Usage tab report real money spent — never an estimate.