Skip to content

Tenancy and layers

DNA separates what a scope declares from who overrides it and how. Two orthogonal mechanisms do that work: layers (overlays that override a base) and tenants (a first-class dimension for multi-tenant deployments). A LayerPolicy governs which overrides are allowed.

This page is the conceptual overview. The mechanics of storing overlays live in the source adapters — see How to write a source adapter.

Scopes and the shared library

A scope is a directory of manifests — the unit you load with Kernel.quick(scope). Scopes are not islands: every scope can inherit shared documents from a sibling .dna/_lib/ library scope. Put an agent, skill or theme in _lib and every scope sees it, unless the scope overrides it.

This is the base of the override model: _lib provides shared defaults; a scope specialises them.

Layers — overlays over a base

A layer is an overlay: a set of documents that override the base for some dimension without editing the base. The base stays the shared product; a layer carries only the diffs.

The canonical use is a tenant overlay — a per-tenant set of overrides composed on top of the shared base at read time. The adapter resolves a load_layer(tenant) view that merges the tenant's overrides over the base; the base document is never mutated. A tenant that overrides nothing sees the base unchanged.

The merge is by (kind, name) — an overlay document shadows its base twin, everything else passes through:

flowchart LR
    subgraph base ["base scope"]
        B1["Agent greeter"]
        B2["Skill review"]
    end
    subgraph overlay ["tenant overlay"]
        O1["Agent greeter (override)"]
    end
    B1 --> M{"merge by<br/>(kind, name)"}
    B2 --> M
    O1 -->|shadows base| M
    M --> V["tenant view<br/>greeter (overlay) + review (base)"]

Tenants — a first-class dimension

Tenancy is orthogonal to layers, not a special case of them. DNA models it with its own Kinds under the tenant/v1 namespace:

Kind apiVersion What it is
Tenant github.com/ruinosus/dna/tenant/v1 A first-class tenant identity
TenantMembership github.com/ruinosus/dna/tenant/v1 Who belongs to which tenant

Because tenant is a kernel dimension rather than a naming convention, a tenant overlay for one scope does not leak into another — the base for a scope belongs to that scope, and each tenant sees the base plus its own diffs.

LayerPolicy — which layers may override which Kinds

Not every Kind should be overridable by every layer. A LayerPolicy (github.com/ruinosus/dna/policy/v1 · LayerPolicy) declares which layers may override which Kinds — the guardrail on the override model. It is data, like everything else: a policy document, validated and versioned.

The maxim: inheritable ⇒ never per-tenant-only

A design invariant worth stating plainly: a Kind that is an inheritable default — one a scope inherits from _lib and may override — must be writable at the shared base. Reading such a Kind promises a base default that overlays can specialise; a storage mode that forbids writing that base would contradict the read contract. So inheritable Kinds use a permissive or global tenancy model, never a strictly per-tenant one. Per-tenant-only storage is reserved for data that has no shared default (audit logs, per-user profiles, and the like).

Where to go next