Multi-Tenancy
LeafMesh supports per-request tenant scoping so a single deployment can serve many end-customers with fully isolated state. This is the model an embedding SaaS uses to run one LeafMesh agency on behalf of all of its own customers.
How it works
Pass a tenant identifier on the request via the X-Tenant-ID header:
POST /api/mesh/request
X-API-Key: <your org key>
X-Tenant-ID: acme-corp
Content-Type: application/json
{ "data": { ... }, "entry_point": "support", "session_id": "…" }
Every piece of state the request touches — sessions, agent yields, memory, events, follow-ups — is isolated per tenant, on both the Redis and Postgres backends. Tenant A can never see or wake Tenant B's sessions, and memory recall is scoped to the tenant. Global event streams are stamped with the tenant and filtered on read, so cross-tenant delivery cannot happen.
Trust model
The tenant identifier is pass-through — LeafMesh does not mint or validate it; it trusts the value your application supplies. Your product is responsible for authenticating the end-customer and setting the correct X-Tenant-ID. This keeps LeafMesh unopinionated about your identity system while giving you hard isolation of state.
The tenant id stays out of model calls
From 2.4.488 the tenant id is left out of the prompt, the request to the model provider and the payload sent to outside agents. It appears only in your own traces (leafmesh.tenant_id). Outside connectors still receive the X-Tenant-ID HTTP header. A tenant can also use its own model key: see Tenants' own model keys.
A tenant joining a session later
2.4.491+. A session that started without a tenant and later receives an
X-Tenant-ID keeps its data where it started. From then on that tenant pays
for and makes its requests: its keys, its model and its id on traces, and
other calls on that session are billed to it too. A request from a different
tenant is refused (403 tenant_mismatch).
A Slack or other channel reply in a tenant conversation's thread reaches that conversation: the tenant comes from the session, and the person never sends it.
Who paid for a model call
2.4.491+. Every model call's trace carries leafmesh.paid_by:
tenant_key or organisation_key. A tenant's own key failing logs one quiet
warning line; your organisation's key failing stays a loud error.
Without a tenant
If no X-Tenant-ID is sent, requests run in the default (untenanted) scope — the normal single-tenant behavior. You can adopt tenancy incrementally: existing requests keep working, and you add the header where you need isolation.
Scheduled work stays scoped
A follow-up (schedule_followup) scheduled inside a tenant's request fires back inside that same tenant's context, so a deferred continuation never crosses tenant boundaries.