Skip to content

Serve Your Own Customers

Model Your Application in Caracal compares the isolation models at a high level. This page is the end-to-end pattern for the most common case: your application is a single product that serves many of its own customers, and you want clean per-customer authority without standing up infrastructure for each customer.

The short answer: run one zone for your deployment and represent each customer as a subject. Customer separation lives below the zone — in subjects, sessions, agents, and delegation — which is exactly where Caracal models per-actor authority. You do not create a zone per customer.

LayerOwnsPer-customer?
ZoneSigning keys, policy set, resources, providers, audit trailNo — one per deployment
ApplicationYour product’s service identityNo — shared by all customers
Subject sessionThe authenticated customer the work is for (sub)Yes — one per customer
Agent sessionOne agent run, bound to a subject session, carrying a customer correlation key in metadataYes — one per customer task
Delegation edgeThe scoped authority that agent holds for one resourceYes — least privilege per task

A zone is the trust boundary that owns keys, policy, and audit. A customer is not a trust boundary of its own here; it is an attribute of the subject and the work, checked by policy and recorded in audit. Keep customer identity in the subject and metadata — never in scope names.

Provision a single zone for the deployment and one managed application for your product, as in Model Your Application in Caracal. Set the application identity once in the environment; it is shared across all customers.

Terminal window
CARACAL_ZONE_ID="<your-zone-id>"
CARACAL_APPLICATION_ID="<your-application-id>"
CARACAL_APP_CLIENT_SECRET="<your-application-secret>"

The zone owns one policy set and one audit trail. Every customer’s authority is decided and recorded inside it.

When a customer signs in, mint a subject session whose sub is your stable customer identifier. The subject is the customer’s identity inside Caracal: STS records the session as a user session, and the sub flows into every downstream agent, delegation, and audit event.

Use a customer id that never changes for the life of the account, so audit history stays attributable even after profile changes. Do not reuse one sub across two customers — separation is only as strong as your subject issuance.

Step 3: Spawn a per-Customer Agent with a Correlation Key

Section titled “Step 3: Spawn a per-Customer Agent with a Correlation Key”

Spawn the agent for a customer’s request inside the one zone, bound to that customer’s subject session, and stamp the customer id into the agent’s metadata. The metadata travels with the agent session and makes per-customer attribution a direct lookup instead of a guess.

async with caracal.spawn(
metadata={"customer_id": customer_id},
) as ctx:
# Every gateway and provider call in this block carries a scoped,
# non-root mandate for this customer's subject.
await do_work(ctx)

subject_session_id references the STS session, so it is the authority anchor; the customer id in metadata is the business correlation key. Use a single, consistent metadata key — customer_id is the recommended convention — so audit queries and dashboards can filter by customer without per-app special cases.

For fan-out work, give each child agent the least authority it needs with delegation, keeping one customer’s blast radius contained even though all customers share the zone.

When the work is app-initiated — a billing sweep, a dunning cycle, a scheduled job acting on a customer’s records without that customer signing in — there is no subject session to anchor the customer. Carry the customer on the agent session itself with a label:

async with caracal.spawn(
grant=Grant.narrow(["billing:collect"]),
labels=[role, f"customer:{customer_id}"],
metadata={"customer_id": customer_id},
) as ctx:
await collect_overdue(ctx)

Labels drive the platform decision contract’s confinement: a confinement data document caps every customer-labeled agent to the customer-record surface — whatever its role would otherwise allow:

# caracal:data-document
package caracal.authz
import rego.v1
confinement := [{
"label_prefix": "customer:",
"scopes": ["billing:collect", "billing:read"],
}]

Publish this confinement document and a worker spawned for one customer can never mint authority outside that surface, even by accident. The Lynx Capital example ships this confinement data with tests.

Step 4: Differentiate Authority per Customer

Section titled “Step 4: Differentiate Authority per Customer”

All customers share one zone policy set, but authority is per-request and label-aware. The agent’s role labels and the delegation edge are in the platform decision contract’s input, and your grants map roles to scopes per resource. Model plan or tier differences as roles: give the scale-plan role the resource scope, withhold it from the others, and label each customer’s session with the role it earns.

# caracal:data-document
package caracal.authz
import rego.v1
grants := {
"resource://payouts": {
"application": "payments",
"roles": {"scale-plan": ["payouts:run"]},
},
}

A session spawned for a scale-plan customer carries the scale-plan label and mints payouts:run; a starter-plan session never holds that role, so the platform contract denies it. Express customer differences as roles in grants and prefixes in confinement — not as a separate policy set per customer. There is one active policy set per zone. See Author Policy Data.

Every decision is written to the zone audit ledger with the application, session, agent session, and delegation chain. The customer id rides in the agent metadata you set in Step 3, and the subject is reachable through the session, so you can answer “what did this customer do, with what authority” from the audit trail. See Audit and Request Traces.

To build a per-customer read-only view — for an internal support console or a customer-facing activity page — query the shared trail by the convention you already established: filter agent sessions on the customer_id metadata key (or the customer: label), then collect each session’s decisions, delegations, and Gateway events. Because every session carries the key from spawn, the view is a filter over one audit trail, not a separate per-customer store. The same filter scopes Console searches and SIEM exports.

To cut a customer off, revoke that customer’s subject session and its agents and delegation edges; cascade revocation tears down the chain beneath them. There is no single “purge everything for a customer” call, so iterate the customer’s sessions and agents. See Sessions and Revocation.

One shared zone serves many customers well, with two ceilings to design around. The agent concurrency caps and the rate-limit scope are in Defaults and Limits.

  • Concurrent agents per zone are capped, so a single zone bounds how many customer agents can run at once. If you need more simultaneous customer agents than one zone allows, add applications within the zone, or move the busiest customers to their own zone.
  • STS rate limiting is per zone, resource, and acting application — not per customer. Because customers share your application identity, one heavy customer draws on the shared budget. Keep this in mind for fairness, and isolate a customer into its own zone if it must have a guaranteed independent budget.

Stay with one shared zone unless a customer genuinely requires hard isolation: independent signing keys, an audit trail that can never mix with others, or a policy change that can never affect another customer. Those needs are the zone per customer model in Model Your Application in Caracal.

In the open-source edition you provision and automate those zones yourself through the Admin API, and your one application authenticates separately into each. A scoped Control key cannot create zones — it is bound to the single zone it was issued in. Managed per-tenant lifecycle, where customer onboarding mints and operates a zone automatically, is an Enterprise Edition feature; see Compare Editions.

  • Sign in as two different customers, run the same workflow, and confirm each produces a distinct subject and agent session in the Console.
  • Author grant data that gives one customer’s role a resource and withholds it from another, then confirm both decisions in the audit trail.
  • Revoke one customer’s session and confirm its in-flight agents lose authority while the other customer is unaffected.