MemClaw / docs
Concepts

Cross-tenant credentials

Read across every tenant in your org with a single credential — for admin agents, analytics, and rollups.

A cross-tenant credential lets one agent read memories, documents, and stats across multiple tenants in an org without rotating keys when tenants are added or removed. Writes still pin to the credential's home tenant — the widening is read-only by construction.

The headline use case: a single Admin agent that summarises activity across every fleet, surfaces conflicts that span teams, or builds an org-wide knowledge view. Mint the credential once; it stays current as your org grows.

What the kind buys you

CapabilityTenant-scoped (user_api_key, agent_key)Cross-tenant (cross_tenant)
Read from home tenant
Read from sibling tenants in the org❌ (403)
Write to home tenant
Write to sibling tenantsn/a❌ (403 — writes pin to home)
Auto-include tenants added after mintn/a✅ (with read_all_org_tenants=true)
Wire prefixmc_mc_ (kind is on the row, not the prefix)

Two ways to scope the read set

Pick one at mint time:

source_tenant_ids — a frozen, explicit list

Pin the readable set to specific sibling tenants. The list is recorded on the credential row; adding a new tenant to the org does not widen this credential. Rotate the credential to include new tenants.

Use when the cross-tenant relationship is bounded by intent (e.g. "the EU rollup agent reads from eu-sales, eu-support, eu-product and never anywhere else"). A future tenant added to the org should not be readable by this credential without an explicit decision.

read_all_org_tenants=true — live resolution

Re-resolves the readable set on every authenticated request by querying the current enterprise.tenants membership for the credential's org. New tenants are auto-included on the next request — no rotation needed. Removed (soft-deleted) tenants disappear from the readable set on the same cadence.

Use when "every tenant in the org" is the policy — the org Admin agent, the compliance reporter, the cross-team weekly summary.

The two modes are mutually exclusive — a single credential carries either an explicit source_tenant_ids list or read_all_org_tenants=true, never both. The admin-api enforces this at mint time.

Mint via the dashboard

Settings → Organization → API Credentials → Add credentialCross-tenant. The wizard presents a binary "Read scope" toggle:

  • Home tenant only — produces a regular user_api_key/agent_key row.
  • Read across all org tenants — produces a cross_tenant row with read_all_org_tenants=true. The static-list variant (source_tenant_ids) is reserved for the API; the dashboard intentionally surfaces only the live-resolution mode because the audit-evidence trail concluded most operators mean "all, current and future" when they ask for cross-tenant.

The raw_key is returned once. Treat it like a password.

Mint via the API

curl -X POST "https://memclaw.net/api/v1/admin/orgs/$ORG_ID/api-credentials" \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "cross_tenant",
    "home_tenant_id": "admin-fleet",
    "read_all_org_tenants": true,
    "label": "org-admin-summary-agent",
    "capabilities": ["read", "write"]
  }'

For the static-list variant, swap read_all_org_tenants: true for source_tenant_ids: ["sibling-a", "sibling-b"].

What you'll see on the wire

The gateway plumbs the resolved scope on every request:

X-Tenant-ID: admin-fleet
X-Readable-Tenant-IDs: admin-fleet,sibling-a,sibling-b,sibling-c
X-Capabilities: read,write
X-Auth-Mode: cross-tenant

/whoami surfaces the same shape so you can verify your credential without probing each tenant:

{
  "tenant_id": "admin-fleet",
  "readable_tenant_ids": ["admin-fleet", "sibling-a", "sibling-b", "sibling-c"],
  "capabilities": ["read", "write"],
  "auth_mode": "cross-tenant",
  "via_gateway": true
}

Which surfaces honor the widening

Cross-tenant credentials widen WHERE tenant_id = $home to WHERE tenant_id = ANY($readable) on these read paths:

  • MCP tools: memclaw_recall, memclaw_list (scope=fleet|all), memclaw_stats (scope=fleet|all), memclaw_doc ops search/read/query/list_collections.
  • REST search/recall: POST /search, POST /recall, POST /documents/search.
  • REST aggregate lists: GET /memories (when no explicit tenant_id query param), GET /memories/stats, GET /documents/collections.

For the storage-routed reads (GET /documents/{id}, POST /documents/query, GET /documents, GET /memclaw/keystones), cross-tenant credentials access one tenant at a time — pin tenant_id to any tenant in your readable set and the gate allows it.

Writes always pin to home_tenant:

  • memclaw_write with tenant_id not equal to home → FORBIDDEN.
  • POST /documents (write) to a sibling tenant → 403.
  • POST /memories to a sibling → 403.

The principle: the credential carries {read, write} capabilities, but enforce_tenant on the write target rejects anything that isn't home. Reads use enforce_readable_tenant, which accepts any tenant in the readable set.

Audit trail

Every widened read emits a cross_tenant_read audit event per source tenant the query touched. Each event is logged TO the source tenant (so per-tenant audit-log queries surface "who read FROM my tenant") with the home tenant_id and agent_id in detail for forensic traceability:

{
  "action": "cross_tenant_read",
  "tenant_id": "sibling-a",
  "agent_id": "admin-rollup-bot",
  "resource_type": "memclaw_recall",
  "detail": {
    "home_tenant_id": "admin-fleet",
    "home_agent_id": "admin-rollup-bot",
    "result_count_from_this_tenant": 7,
    "query_summary": "what did we ship last week"
  }
}

Single-tenant credentials emit zero cross_tenant_read events — the audit is gated on the readable set being wider than home.

Operational notes

  • Live resolution on every request. The read_all_org_tenants=true path queries list_tenants_by_org on every authenticated call. For high-traffic admin agents this is the next caching target — a short in-process LRU keyed on org_id would amortise the round-trip without making new tenants invisible for longer than feels noticeable.
  • Fail-open on storage degradation. If the live-resolution lookup fails, the credential degrades to home-only rather than 5xx-ing the whole request. Logged for observability; the credential keeps working with reduced scope until storage recovers.
  • Revocation is immediate. Setting revoked_at short-circuits the resolver before the org-tenants lookup — revoked credentials can't even probe org membership.
  • The on-the-wire mc_ prefix is shared. Tenant-scoped and cross-tenant credentials look identical at the protocol layer. The dashboard's "Kind" column tells you which is which.

When NOT to use cross-tenant

  • Per-fleet bots that should only see their own tenant's memories — use a regular agent_key. The trust-and-fleet system handles intra-tenant scoping; cross-tenant widening is for org-wide views.
  • Customer-facing integrations where a single org's tenants represent different customer accounts — use one tenant-scoped credential per customer, not one cross-tenant credential that erases the boundary.
  • Write-heavy agents — cross-tenant only widens reads. If your agent's primary job is writing across many tenants, run one tenant-scoped agent per tenant instead and use a coordinator pattern at the application layer.
  • Trust levels — the per-agent permission scheme that gates reads/writes within a tenant.
  • Governance — org/tenant/agent hierarchy and how cross-tenant credentials fit into it.
  • Per-agent keys — agent-scoped credentials, the more common scope for production fleets.