Recipes← all recipesView on GitHub

Configure a voice AI agent per request for many tenants

AI Agentsmulti-tenant voice AI

Configure a temporary voice AI agent for each request from tenant data in a query string or header, without changing the deployed agent.

Also called caller ID greeting, personalise the greeting by caller, multi-tenant AI agent

multi-tenant

The claim

set_dynamic_config_callback registers a function the SDK calls on every SWML request with the query parameters, the POST body, the headers and an agent to configure. That agent is an ephemeral copy. Your callback adds a prompt section, a voice and global_data for the tenant the request names. The SDK renders the document from the copy and leaves the deployed agent as it was.

Why it holds

The mechanism is the SDK’s: set_dynamic_config_callback in web_mixin.py, and _create_ephemeral_copy in agent_base.py, which copies the prompt, languages, params and global_data before the callback runs.

How it works

def configure(query_params, body_params, headers, agent):
    key = (query_params.get("tenant") or headers.get("x-tenant") or DEFAULT_TENANT).lower()
    tenant = TENANTS.get(key) or TENANTS[DEFAULT_TENANT]
    agent.prompt_add_section("Tenant", f"You answer for {tenant['name']}. ...")
    agent.add_language("English", "en-US", tenant["voice"])
    agent.set_global_data({"tenant": key, "shop": tenant["name"]})

class FrontDeskAgent(AgentBase):
    def __init__(self):
        super().__init__(name="front-desk", route="/front-desk")
        self.prompt_add_section("Role", "You are a bicycle shop's front desk. ...")
        self.set_dynamic_config_callback(configure)

The document rendered for ?tenant=harbor contains this ai object:

{"prompt": {"pom": [{"title": "Role", "body": "..."}, {"title": "Tenant", "body": "You answer for Harbor Bike Repair. ..."}]},
 "languages": [{"name": "English", "code": "en-US", "voice": "rime.marisol"}],
 "global_data": {"tenant": "harbor", "shop": "Harbor Bike Repair"}}

What every tenant shares stays on the deployed agent. What differs is a dictionary here and a database row in production, keyed by the tenant id.

Limitations

SDK 3.0.1 registers the agent’s root only as /front-desk/. A request to /front-desk falls through to a catch-all that answers 200 null. A number pointed at the URL without the slash gets no document. The verifier asserts this so the trap is on record.

This recipe changes the prompt, the voice and global_data per request. You register tools on the deployed agent, and every tenant shares them.

What to change first

Delete the agent.add_language(...) line from the callback and add self.add_language("English", "en-US", TENANTS[DEFAULT_TENANT]["voice"]) to __init__, then run the verifier. Both tenants now render the same voice and Harbor’s assertion fails. That is the point: what sits on the deployed agent is what every tenant gets.

Where this sits

Seen in a build

One of 8 recipes composed by AI drive-thru ordering: a voice agent whose flow your code controls.

Seen in a build

One of 10 recipes composed by Embed a state-aware voice agent in your web page.

Seen in a build

One of 8 recipes composed by Order status desk: one omnichannel AI agent for web chat and phone.

Seen in a build

One of 7 recipes composed by Video blackjack dealer: a voice AI agent that controls the browser.