Multi-Tenant Agent Memory

Give every end user their own agent memory β€” scoped by the data layer, not by a filter your agent has to remember. One Flux API serves the shared knowledge base and the per-user memories; the tenant is part of the URL, so an agent physically cannot read or write across users. Deleting a user removes every memory they own, embeddings included.


The model

Two collections and one strict reference between them:

πŸ“ users                 (parent β€” one resource per end user)
  └── πŸ“ memories        (strict-reference child β€” owned by exactly one user)
πŸ“ help_articles         (shared knowledge, read-only for the agent)

Because memories is a strict-reference child, every memory belongs to exactly one user, and the Flux API path reflects it:

GET  /assistant/users/{user_key}/memories/
POST /assistant/users/{user_key}/memories/          ← agent writes here
GET  /assistant/help_articles/

This is the difference from tag-based scoping (a user_id metadata filter): there is no query without a tenant. A request that names the wrong parent gets a 404 from the ownership check β€” not an unfiltered result set. Over MCP, discover_resources marks the memory collection with required_parents, so a tool call without the user key fails with missing_parent_id instead of silently searching everyone's memories.


Step 1 – Model the collections

  1. Create a Users collection. Keep the schema minimal β€” the resource key is the tenant identifier your app already knows (your internal user id works well as an external key). You don't need to store profile data here.
  2. Inside Users, add a subfolder Memories with strict reference enabled (see Model Parent-Child Relationships for the walkthrough). A typical schema:
    • content β€” text, vectorized (this is what the agent searches)
    • kind β€” select: preference, fact, episode
    • source β€” string (which conversation or tool produced it)
  3. Create your shared knowledge collections (help_articles, etc.) as usual.

Your application creates one users/{key} resource per end user β€” typically at signup, via the Management API or an SDK. The agent never manages users; it only reads and writes memories under one.

Step 2 – Expose one Flux API

Create a Flux API (say, prefix assistant) and connect:

Collectionallowed_methodsWhy
users[]URL structure only β€” the parent segment routes and scopes, but user resources themselves are not readable through this API
users/memoriesget_one, get_many, create, updateThe agent's read-write memory surface
help_articlesget_one, get_manyShared knowledge, read-only

create and update on the memories connection are the per-collection write opt-in from MCP write tools β€” writes stay impossible everywhere else on this API.

Step 3 – Key and role

Create a Flux Access role with read on the API plus create and update grants (Dashboard β†’ Environment β†’ Flux Access), and issue the agent's key with it. The two switches compose: the key's role allows the write tools, the memories connection is the only place writes can land.

Step 4 – Wire the agent

Connect the agent to https://{env_key}.fxns.io/assistant/_mcp (Connect Claude). With the key above, tools/list shows all seven tools. The agent's calls carry the tenant in parents:

{
  "name": "search_records",
  "arguments": {
    "resource_id": "users/{users_id}/memories",
    "parents": {"users_id": "usr_8f2k1"},
    "query": "communication preferences",
    "search_type": "hybrid"
  }
}

Your application injects the current user's key into the agent context (system prompt or tool wrapper); the platform enforces the rest. A call with another user's key under a mismatched chain 404s; a call with no parents fails with missing_parent_id.


Tenant lifecycle

Delete a user, delete their memories. Deleting users/{key} (Management API) cascades through the ownership chain: every memory the user owns is deleted, along with its vector embeddings and its storage footprint on both billing axes. The cleanup runs asynchronously in batches and completes within minutes β€” one deletion call is a full, GDPR-grade erasure of the tenant.

Export a user. query_records (or the REST list endpoint) under users/{key}/memories/ returns exactly one tenant's data β€” paginate and you have a complete export.

Bill predictably. Memories are ordinary records: writes are weighted by embedded tokens, searches are 1 retrieval each. A short preference memory is 1 write.


Why not a user_id filter?

You can model tenancy as a user_id field plus a filter on every query β€” it's how most memory stores do it, and FoxNose supports it too. The failure mode is silent: the one query where the agent forgets the filter returns other users' data as if nothing were wrong. LLM agents forget optional arguments.

Strict reference moves the scope from a convention into the data layer. The tenant is in the path, the path is required, ownership is verified per request, and deletion follows the same chain. The agent can't hold it wrong β€” and when it tries, it gets an explicit error instead of someone else's memories.

Was this page helpful?