Extending Zero Trust to Your Agents' Memory A developer working on the Lakekeeper catalog built a runnable example extending zero trust to agent memory, in which two agents keep private memory in a single catalog, propose skills they cannot publish, and a human promotes them. The implementation found that row-level scoping cannot be enforced at the storage layer, so isolation must be dataset-level — entries and embeddings together — with the rule that the dataset is the unit of access and must equal the unit of trust. The developer also notes that granting modify confers select, forcing per-agent proposal queues rather than a shared inbox. Assume the agent is compromised. Then decide where its memory lives. I ended a piece two weeks ago with a claim: a catalog is where governed agent memory starts, and a control plane is what it has to become. Claims are cheap. So we built it, as a runnable example https://github.com/lakekeeper/lakekeeper/tree/main/examples/agentic-memory — two agents keeping private memory in one catalog, proposing skills they are unable to publish, and a human promoting them. I work on the catalog in question, so read the comparisons below with that in mind. The frame is zero trust, pointed at something that has mostly escaped it: nothing in the agent's code enforces any of this. The agent is assumed hostile, and every refusal comes from the catalog. It works. Rather more interesting is what had to change on the way: four points where the system, rather than an argument, decided how this had to be built. The obvious design is one memory store with a column for whose memory it is. Nearly everything does this: mem0 scopes with a user id , a vector database with a metadata filter, Databricks with a key. It is the shape anyone would draw first. It cannot be enforced. A catalog that vends credentials issues them for a location : a prefix in object storage, scoped to a dataset. Everything under that prefix is reachable by whoever holds the keys. There is no row-level anything, because at the moment of enforcement there are no rows — there are bytes at a path, and a credential that either covers the path or does not. And there is a second leak that row scoping does not even reach. Memory is not only entries; it is the embeddings built over them. One shared recall dataset lets anyone who can read it vector-search every memory it indexes, whatever the paths inside look like. Similarity does not respect a whose column. So a scope is a dataset — entries and embeddings together. If two agents must not read each other's memory, they get two datasets, in two namespaces, with two grants. Not two row filters. Dataset here means the catalog's unit of registration, not an Iceberg table: a dataset of entries, a Lance index beside it, each a separate object with its own grant. Only the decision log at the end of this piece is Iceberg. That sounds like a limitation and is closer to a clarification. Row-level isolation in a memory service is a promise the application makes; dataset-level isolation is a promise the storage layer keeps. One of those survives a bug in the agent framework. It has a cost, and I would rather state it than not: per-user isolation means a dataset per user. Where a team or a tenant is the real boundary, one dataset for the group is correct and cheaper. The rule that falls out is short: the dataset is the unit of access, so it has to equal the unit of trust. The design had agents filing skill proposals into a shared queue they could write but not read. An inbox: you may post, you may not rummage. The model will not express it. Reading data resolves through a relation that includes writing it, so granting modify confers select — and can drop , can rename and can set protection with it. A shared queue would have been readable by every agent that could write to it, and droppable by any one of them. So each proposer gets its own queue — its own dataset, named for it. Proposals stay private, and attribution stops being a field the caller fills in and becomes a fact about which dataset the write landed in. An agent cannot file under another agent's name because it has no credentials for that dataset. The catalog has governance tags — a controlled vocabulary you attach to objects. pii , sensitivity=restricted , a lifecycle tag of your own. The useful part is not the word, it is who may apply it. Attaching a tag is delegated separately from the right to change the data, so a compliance reviewer can label something they cannot read — and someone who can rewrite a dataset cannot quietly strip the label that marks it sensitive, because removing a tag is gated on the tag rather than on the object. Classification becomes a power in its own right, held by whoever should hold it. What a tag does not do, under the relationship model this build runs, is decide access: no authorizer reads the value, and the grant stays the gate. Under Cedar — which the catalog also supports — policy decides from attributes on the resource, and that is the direction this is going. The label and the decision converge. Human review does not scale. Forty proposals arrive overnight, one matters, and the reviewer reads all forty or none. The obvious fix is an agent that triages them, and it is a trap. A governance agent is, by construction, a machine that reads attacker-controlled text. A proposed skill is exactly that — it was written by an agent that may itself have been compromised, which is the case the review exists to catch. That is assume-breach applied to an agent rather than a network: the proposer is treated as already hostile, and every refusal in this piece has to hold in that case. Put a model in charge of the verdict and you have built something that reads hostile input and holds the publishing credential. A skill that cannot get past a regex can absolutely get past a model by addressing the reviewer instead of the task. So the verdicts are deterministic. Rules, not judgement: every proposal comes out ok , warning or critical , and the queue is ordered worst first. Triage decides what the human reads first, never what happens to it. The model writes a one-line summary alongside and touches nothing else. Rules cannot be argued with, they replay identically tomorrow, and they can be shown to an auditor. Then the same question one level up: who may change the rules? Because whoever can weaken a rule turns triage into theatre — silently, with no diff for anyone to review. So the rules are governed data, not code. They live in the catalog as a policy document. The governance agent reads them and cannot write them. Which comes down to one sentence: The governance agent can read the rules it enforces. It cannot change them, and it cannot approve anything. Both refusals come from the catalog. Neither is a check in its own code that a clever prompt could talk past. The shape of the thing, once all of that settles: agent memory/ ├── agent a/ entries + recall agent-a: read+write · agent-b: invisible ├── agent b/ entries + recall agent-b: read+write · agent-a: invisible └── shared/ entries + recall both read, neither writes skills/ ├── proposed/ │ ├──