# Tenancy (/docs/concepts/tenancy)



Ductor is multi-tenant from the ground up. A tenant identity isn't bolted on at
the API edge — it flows through every layer, so isolation is a property of the
whole system rather than a check in one place. This page explains where tenancy
lives; [Entitlements](/docs/concepts/entitlements) covers what a tenant is
*allowed* to do once it's isolated.

## Tenancy is an end-to-end invariant [#tenancy-is-an-end-to-end-invariant]

Every object Ductor persists is tenant-scoped: pools, rules, connections,
workflow definitions, and every workflow run. The tenant identity travels with:

* **Every request** — carried from the transport boundary through the
  application layer.
* **Every workflow tick** — a run's tenant is loaded with its state and stays
  attached through the whole tick.
* **Every storage query** — reads and writes filter on the tenant.
* **Every cache key** — cached pools and rules are keyed by tenant, so one
  tenant's cache can't serve another's data.
* **Every capacity counter** — the atomic Redis counters that enforce
  concurrency and rate limits are per-tenant.

Because the scoping is uniform, there's no single "did we remember to filter by
tenant here?" gap. Isolation holds at storage, at cache, and at the hot path
simultaneously.

## Why isolation comes first [#why-isolation-comes-first]

Tenant isolation is the *precondition* for everything in
[Entitlements](/docs/concepts/entitlements). A quota only means something if one
tenant's usage can't be charged to another; a plan ceiling only holds if load
from tenant A can't consume tenant B's capacity. So the ordering is
deliberate: isolate first, then enforce limits on top of clean boundaries.

## The fair-scheduling connection [#the-fair-scheduling-connection]

Tenancy also drives *fairness*, not just *isolation*. The
[tiered fair queue](/docs/concepts/tiered-queue) schedules work **tenant-first**:
it picks the earliest-due tenant, then that tenant's earliest work, so a tenant
with thousands of partitions can't starve a tenant with a handful. Fairness and
isolation are two faces of the same tenant scoping.

## System tenant [#system-tenant]

Not all work belongs to a customer. Internal machinery — most visibly
coordinator wakeups — runs under a reserved **system tenant** on a dedicated
partition. This keeps engine-internal traffic from competing with customer work
in the fair scheduler and keeps the "every item has a tenant" invariant intact
even for infrastructure tasks.

## Run lineage within a tenant [#run-lineage-within-a-tenant]

Sub-workflows and ContinueAsNew create *related* runs, and that lineage is
tracked without ever crossing a tenant boundary. A run carries its
`parent_run_id`, `root_run_id`, and `nesting_depth`, so a tree of child runs and
a chain of ContinueAsNew successors all stay attributable to one tenant and one
originating request. See
[The DAG Workflow Model](/docs/concepts/dag-workflow-model#continueasnew).

<Callout title="One identity, everywhere">
  The practical rule: if a piece of data or a unit of work exists in Ductor, it
  has a tenant. Storage rows, cache entries, capacity counters, and queue items
  all carry one — which is what makes "tenant isolation" a structural guarantee
  rather than a review checklist.
</Callout>

## Where to go next [#where-to-go-next]

* [Entitlements](/docs/concepts/entitlements) — plan and quota enforcement on top of tenant isolation.
* [The Tiered Fair Queue](/docs/concepts/tiered-queue) — tenant-fair scheduling.
* [The Routing Pipeline](/docs/concepts/routing-pipeline) — where tenant-scoped decisions are made.
