FabricFabricHarness
Operating

Multi-tenancy

Stamp tenant ids on sessions, scope listings and reads per tenant, and gate webhook deliveries by tenant header.

fabric-harness exposes a single concept for multi-tenancy: an opaque tenantId string. fabric-harness never interprets it — host applications map it to whatever identity model they own (organizations, customers, projects, workspaces). What fabric-harness provides:

  • Stamping: every entry, event, and approval emitted by a session carries tenantId.
  • Cross-tenant guard: appending to a session that belongs to a different tenant throws.
  • Listing filter: listSessionSummariesFromStore(store, { tenantId }) returns only that tenant's sessions.
  • Server scoping: fh server reads X-Fabric-Tenant (or ?tenant=) and applies the filter + 404s on cross-tenant reads.

Stamping a session

const fabric = await init({
  tenantId: 'tenant-acme',
  // ...other options
});

const session = await fabric.session();   // tenantId inherited
// or override per-session:
const ad = await fabric.session(undefined, { tenantId: 'tenant-globex' });

tenantId propagates from AgentInitSessionOptions → every SessionEntry, FabricEvent, and ApprovalRequest the session emits.

Cross-tenant guard

If two init() calls (possibly from different hosts) target the same session id but disagree on the tenant, the second one throws POLICY_DENIED. This catches accidental cross-tenant writes early — better than silent corruption.

CLI

fh sessions --tenant tenant-acme            # filter listing
fh run hello --tenant tenant-acme           # stamp on the spawned session
fh export-audit <id>                        # tenantId column appears on every row

HTTP server

curl -H 'X-Fabric-Tenant: tenant-acme' http://localhost:9111/sessions
curl -H 'X-Fabric-Tenant: tenant-acme' -d '{"text":"hi"}' \
  http://localhost:9111/agents/edge-summarizer/$(uuidgen)
  • Listing: only sessions stamped with the matching tenant are returned.
  • Read: a 404 is returned when the loaded session's tenant differs from the header (no information leak about session existence).
  • Write: spawned session inherits the header's tenantId.

Set FABRIC_HARNESS_TENANT_REQUIRED=1 to reject any request that omits the header.

Identity layer is yours

fabric-harness deliberately doesn't ship a "tenants table" or auth integration. Bring your own:

  • Map X-Fabric-Tenant to your SaaS tenant ids in your reverse proxy.
  • Enforce per-tenant cost ceilings via costLimit.perScope + scopeKey. Use tenantId as the scope key.
  • Combine with approvalRules.audience — emit approvals to per-tenant audience channels (reviewer:tenant-acme).

See also