Multi-tenancy
Most production workloads serve more than one customer. Papayya's partition_key convention makes the tenant a first-class slice of the run → item ledger, so the dashboard, queries, cost rollups, and slice replay can read it without you wiring any custom infrastructure.
Tenant is the taught face of partition_key. You declare it once, at the loop boundary, as a callable over each element:
partition_key=lambda t: t["tenant"]The mechanism is general — partition_key can identify a tenant, a customer, an end-user, or a region — but tenancy is the dominant use case, so that's how we teach it.
Papayya makes tenant a declared dimension of your own execution — a key it items and lets you slice by. It does not operate your tenancy: no isolation boundaries, no per-tenant quotas, no provisioning. You bring the key; Papayya makes it first-class in the ledger.
Declare the tenant at the call site
Pass partition_key= (and item_id=) when you invoke your @agent. Papayya consumes both and does not forward them to your function, so nothing in your signature changes:
import papayya
from papayya import agent
@agent(name="triage-ticket", model="claude-sonnet-5")
def triage(run, ticket: dict) -> str:
...
for ticket in TICKETS:
print(triage(
ticket,
item_id=ticket["id"], # your identity for this item
partition_key=ticket["tenant"], # whose item it is — the tenant
))You declare the tenant where the work starts — not threaded through any function signature. If you want to read it, declare partition_key in your own signature and it is passed through as well.
The two cloud paths take the same two values:
papayya run triage-ticket '{"id": "t-9", "tenant": "acme"}' \
--item-id t-9 --partition-key acmeand the dashboard's Run now dialog has an Item ID and a Tenant field.
Where the key lands
Papayya writes the value into the indexed partition_key column on both the run and the item rows it mints. That single indexed column is what powers every per-tenant view downstream — no schema changes on your side.
What a missing key costs
partition_key is optional, and omitting it is not an error — the column is left NULL. But a NULL partition key is invisible to every per-tenant slice: Explore's Partition column renders a dash, and "which of my customers is broken?" has no answer for that item. Declare it on anything that belongs to a customer.
What this unlocks
Because the tenant is a first-class column on runs and items, you get per-tenant lenses on your own execution for free:
- Per-tenant filters in the dashboard — list runs and items for a specific tenant.
- Per-tenant outcome — see the
degraded_countandworst_outcome_statusfor one tenant's slice of a run. - Cost rollups — aggregate per-item cost by tenant for invoicing or chargeback.
- Slice replay — recover just one tenant's not-ok items:
papayya replay --run <id> --tenant acme.
These are all views and slices over execution Papayya already owns — not tenancy machinery Papayya runs on your behalf.
Schema
The SDK persists the key locally (offline store) and in the cloud (control-pane migration). Both stores expose:
| Column | Type | Notes |
|---|---|---|
runs.partition_key | TEXT, indexed | Value returned by partition_key(element) for the run. |
items.partition_key | TEXT, indexed | Same key denormalized onto each item for per-tenant queries. |
Rows written without a partition_key in scope have NULL in both columns and are unaffected — the convention is opt-in per loop.