Multi-tenant SaaS
If you build SaaS, you’ve solved this problem before in your own app:
“how do I keep customer A’s data away from customer B?”. Mnemosyne
inherits the most rigorous answer there is — PostgreSQL Row-Level
Security with FORCE.
The setup
const mnemo = new MnemosyneClient({
baseUrl: "http://localhost:3000",
apiKey: process.env.MNEMO_KEY!,
// workspaceId is determined per-request from the authenticated user
});
app.get("/recall", auth, async (req, res) => {
const result = await mnemo.recall({
workspaceId: `tenant:${req.user.tenantId}`, // ← the key line
query: req.query.q,
});
res.json(result);
});What you don’t have to write
You don’t write WHERE tenant_id = $1 anywhere. The Postgres role that
Mnemosyne uses has FORCE ROW LEVEL SECURITY set on every table — even
the application itself can’t bypass it. Every query Mnemosyne issues
runs under the GUC app.workspace_id, and RLS rewrites every read and
write to enforce isolation.
-- migration 0015_mnemo_rls_foundation.sql (excerpt)
ALTER TABLE mnemo_fact ENABLE ROW LEVEL SECURITY;
ALTER TABLE mnemo_fact FORCE ROW LEVEL SECURITY;
CREATE POLICY mnemo_fact_tenant_isolation ON mnemo_fact
USING (workspace_id = current_setting('app.workspace_id'));If your application has a SQL injection bug and someone manages to issue
SELECT * FROM mnemo_fact, they still only see the rows for the
current GUC.
The actor isolation policy
Above and beyond per-workspace isolation, Mnemosyne also enforces
per-agent isolation when mnemo_access_policy is configured. A support
bot can be told “you can only see facts authored by support bots and
facts marked tier:public” — and the database enforces that even
against application bugs.
See migration 0040_mnemosyne_actor_isolation_policy for the details.
Backup isolation
Workspaces are also the unit of export and import. mnemo export dumps the
workspace tied to the API key you authenticate with (gzip JSONL to stdout),
so use that tenant’s key:
MNEMO_KEY=<acme-corp key> mnemo export > acme-corp.jsonl.gz
MNEMO_KEY=<restore key> mnemo import acme-corp.jsonl.gz --from mnemo --conflict-resolution skipA customer asks for their data → one command. GDPR Article 20 (data portability) is satisfied without writing any custom code.
Cross-tenant aggregation
If you have legitimate need to look across tenants (admin dashboards,
billing aggregation), Mnemosyne exposes a mnemo_org_fact_view (migration
0050) that requires a separate scope (memory:admin:cross_tenant).
Without that scope, the view returns zero rows even to the application.
Multi-tenant safety is not a feature you reach for in a sprint — it’s the foundation everything else sits on. We built it first, before any cognitive feature.