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 serverreadsX-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 AgentInit → SessionOptions → 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 rowHTTP 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-Tenantto your SaaS tenant ids in your reverse proxy. - Enforce per-tenant cost ceilings via
costLimit.perScope + scopeKey. UsetenantIdas the scope key. - Combine with
approvalRules.audience— emit approvals to per-tenant audience channels (reviewer:tenant-acme).