DEV Community

Michael Deng
Michael Deng

Posted on

The Enterprise Coming-of-Age of a Memory Engine: Multi-Tenancy, Auth, Audit, and Backup Drills

Every feature described here is live in production (on our own servers). Commits: 8e9a83c / 138c663 / cbf9958, verifiable in the public repo.


The Flag We Planted Two Weeks Ago, Delivered

In post #8 of this series we said something harsh: most memory engines on the market are personal toys, and to cross into enterprise territory you have to pass four gates — shared-service architecture, production-grade performance, an org-level data model, and freshness and governance of knowledge.

At the time we had cleared the first two (single-binary gRPC service, 12,494 TPS); the last two were still predictions. Over the past two weeks we built the remaining gates: enforced multi-tenant isolation, three-tier API key authentication, an audit event stream, and periodic checkpoints with backup drills — all shipped and deployed on our own production servers.

As usual, this post is not a feature list. It covers how the four things were done, and two problems we discovered along the way that sent a chill down our spines.

All four gates built

Multi-Tenant Isolation: Starting from a Real Leak

In enterprise settings, one memory store serves multiple departments and multiple projects. Isolation is not "stored separately in principle" — isolation must be enforced on every single retrieval path.

While building L2.1 we found a real leak: the HNSW global vector index does not filter by tenant. Vector seeds and Search RPCs queried the global index directly and only filtered by owner/tenant afterwards. Which means a query vector from tenant A was effectively running similarity computations over tenant B data. The results would be filtered after the fact, but "over-fetch 4x and filter" already exposes the retrieval space to a query that should never see it.

The fix makes every seed channel (vector, lexical, task, recent-fallback) filter by tenant+owner at the source, with a second tenant guardrail on the final results. Cross-tenant GetNode simply returns 404 — not even "does this node exist" is exposed, because existence itself is information.

A related piece done along the way: a zero-migration data format upgrade. The persistence format moved from NYL0 to NYL1 (nodes now carry tenant_id), and old data is backfilled with the default tenant automatically at decode time. 70 nodes and 63 edges in production migrated on restart without losing a single one.

Multi-tenant isolation: a real leak

Three-Tier API Keys: Security Must Not Be a Breaking Change

The biggest enemy of an authentication system is not attackers — it is existing users. If turning on auth requires every client to change its config, auth will never get rolled out.

Our design has three tiers of keys: read / write / admin, each key bound to a tenant (admin can use the wildcard *). But the most important switch is this: without NYLON_API_KEYS configured, the engine runs in open mode, behaving exactly as before. Our own Qoder and Codex clients noticed nothing after the server upgrade — the security capability was in place, and nobody was forced to change configs on a Friday night.

One implementation detail worth mentioning: on the gRPC side, authentication happens in an x-api-key interceptor, but tier checks live in the handlers — because tonic Requests carry no URI, the interceptor does not know which method you are calling. It can only establish who you are; each handler then decides whether who you are qualifies you for what you are trying to do. On the HTTP side, both x-api-key and Bearer are accepted. The MCP bridge and CLI pass NYLON_API_KEY through, so clients add exactly one variable. We also added a genkey subcommand that mints a new key in one line.

The Audit Event Stream: Who Touched Which Memory, When

Among the questions enterprise customers ask about a memory system, one of the most frequent is: "If something goes wrong, can I look it up?"

L2.3 answers with audit.jsonl, an append-only audit file in the data directory. Weave, WeaveSession, Resonate, Search, and DeleteNode are all instrumented — even auth rejections leave a trace. Whoever probed what with a wrong key is one query away.

Two engineering decisions:

  1. Audit writes never do disk IO on the request path. Events go to a background writer thread, so RPC paths carry zero disk overhead. A 5,000-entry in-memory ring buffer serves fast queries, preloaded from the tail of the file at startup, so the audit page is not blank after a restart.
  2. Tenant isolation extends to the audit log itself. A tenant-bound key calling GET /v1/audit sees only its own tenant events — audit logs are not the administrator private property, and they must not become a cross-tenant information channel.

The web console gained an audit tab (bilingual, Chinese and English) with filtering by operation type and relative timestamps. NYLON_AUDIT=off disables it entirely.

Three-tier auth and the audit event stream

Backup Drills: We Found Our Production Engine Had Never Checkpointed

This is the most important thing from the past two weeks — not because of technical difficulty, but because it nearly went undiscovered.

Our persistence is WAL (write-ahead log) + snapshots (checkpoints). By design, a checkpoint freezes the in-memory graph into graph.snp and truncates the WAL, so restarts replay fast and hot backups have a clean baseline. L2.4 added periodic triggering (NYLON_CHECKPOINT_SECS, default 600 seconds) and manual triggering (POST /v1/checkpoint, admin tier).

Then we ran a drill following our newly written Backup and Restore Manual (docs/BACKUP_RESTORE.md): copy production data to a replica instance, start it, compare stats.

The drill exposed a fact: until that moment, the production engine had never triggered a single checkpoint. It had been running naked on the WAL the whole time — no data loss, but the WAL growing without bound, restart replays getting slower and slower, and no baseline for hot backups. Without this drill, the problem would have stayed dormant until it exploded during a real recovery.

Drill results: the replica instance came up fully consistent with production (70 nodes / 63 edges), the first checkpoint wrote graph.snp to disk successfully (313 KB), and the WAL truncated to zero.

This deserves a sentence of its own: a backup plan that has never been drilled equals no backup at all. Every company operations manual has a backup procedure; the only difference is whether you have actually restored something once.

For Kubernetes users we prepared a Helm chart (helm/nylonme): single-writer Deployment + PVC + Service + Secret, with optional embedded ollama that pulls the embedding model automatically after install.

Backup drill

Meanwhile: One More Piece of the Ecosystem Puzzle

In the same batch of work, the LangChain and LlamaIndex adapters were completed and published to PyPI (nylonme-integrations): on the LangChain side, invoke returns standard Documents; on the LlamaIndex side, retrieve returns NodeWithScore. Both verified against the production engine. If your agent is built on either framework, you no longer need to configure MCP at all — pip install and attach.

Ecosystem: LangChain + LlamaIndex

In Closing

Two weeks ago we wrote that "four gates stand between a toy and infrastructure." Now we can say it more precisely:

The difference between a toy and infrastructure is not in the feature list — it is whether you dare to entrust it with company data.

Whether you dare depends on four things: can it see other people data (multi-tenancy), do you recognize who comes in (authentication), can you look things up when something goes wrong (audit), and can you recover when the worst happens (backup). Two weeks ago the answer to these four questions was "planned." Now it is "running in production."

The memory engine coming-of-age ceremony is over. Next up is the deep water of enterprise adoption: high availability, horizontal scaling, and the continuous evolution of memory quality itself.

NylonME is open source at github.com/nylon-memory/NylonME, Apache-2.0. All features described here are on the main branch; the backup manual is at docs/BACKUP_RESTORE.md.


This is post #10 in the NylonME technical blog series. Previous posts: 01 Phase 1 benchmark / 02 The Memory Engine Is the Core of AI Agents / 03 The Second Half of AI / 04 vs TencentDB-Agent-Memory / 05 From 47 to 84, the Full Record / 06 Choosing an Agent Memory Engine / 07 Two-Minute Agent Integration / 08 Memory Engines, the Next Database / 09 Async Reflection.

Top comments (0)