Skip to content

Securing long-term agent memory on Databricks

Ivan Vydrin10 min read

I built LakeWright.NET for an identity gap. The product knows Alice belongs to Acme Logistics. Databricks may only see the service principal the backend uses for every customer, and if that principal serves everybody, the platform identity is identical for Acme and Globex.

Long-term agent memory is that problem again, with a slower failure mode. A row filter that leaks shows the wrong number once, in one session, to one person. A memory store that accepts a wrong fact keeps serving it to every later conversation until somebody notices the entry and deletes it.

Databricks now gives agents a managed memory store backed by Lakebase, plus memory pipelines that distill durable facts out of past conversations, both in Beta (agent memory, memory pipelines). If we test the quality of data entering a lakehouse, the same question applies to the information entering an agent's memory.

In this post I'll set out how I'd govern a memory store, where bad entries get in, why consolidation makes them harder to trace, and what I'd put between a conversation and a durable memory.

What agent memory is and why it changes the risk

A memory store is the workspace-scoped container for an agent's memories, and creating one provisions the backing Lakebase storage automatically. Each entry holds free-form content, a short description used for retrieval, and three organizing fields: a required actor_id for whose memory it is, an optional session_id recording which session the memory was captured from, and a required path such as /preferences/response-style.md. An entry is uniquely identified by the combination of those three. Retrieval works two ways: list the entries for an actor, optionally filtered by session or path prefix, or search them with a natural-language query ranked by full-text BM25 relevance, per the agent memory docs.

Two things in that design matter more than they look.

First, content is free text. There is no schema to violate, no constraint to fail, no expectation to break. A sentence that is simply wrong is a perfectly valid entry, and every validation habit we have on the lakehouse side is built around a shape instead of a claim.

Second, retrieval is lexical. BM25 ranks on term overlap, so an entry that uses the user's own vocabulary ranks well whether or not it's true, and an entry phrased as an instruction competes on equal footing with entries phrased as facts. With RAG we can at least point at a corpus somebody owns and correct it at the source. Memory is a corpus the agent writes to itself.

User message or tool output

Session store holds the conversation

Memory pipeline distills durable facts

Merge and maintain under actor_id

Memory entry with content and path

BM25 search in a later conversation

Answer the agent gives weeks later

How a sentence in one conversation becomes a durable memory entry and comes back weeks later in another.

Three ways a memory store goes wrong

Poisoned entries. Memory gets written from untrusted input, whatever the user typed and whatever text the agent's tools pulled back from a page or a ticket. A systematic study of memory poisoning in LLM agents identifies four memory write channels and nine structural vulnerabilities across model capabilities, system prompt design and agent architecture, builds a taxonomy of six classes of attack, and reports that agents designed to write and retrieve memory more aggressively are more exploitable, while existing prompt injection defenses fail to cover memory poisoning (Dash et al.). Earlier work came at the same surface from the attacker's side: AgentPoison backdoors agents by injecting a small number of malicious demonstrations into long-term memory or a RAG knowledge base, with no model training or fine-tuning required (AgentPoison).

Stale facts. This is the one I'd expect to bite a client first, because it needs no attacker at all. "Use the finance team's current quarterly forecast sheet for revenue" is true in October and wrong in January. Databricks designed pipelines to skip values from live sources that change over time, such as a table's current row count, and to record how to find or compute the value instead. That's the right instinct. It doesn't cover a fact that was durable when it was captured and expired afterwards, which is most of what a business tells an agent.

Conflicting instructions. The pipeline is designed to keep corrections and explicit feedback, such as which source to use for revenue figures. A user who corrects the agent twice in opposite directions therefore produces two entries with equal claim to authority, and nothing in either entry's content says which one came last.

Memory consolidation and traceability

Each pipeline run processes every actor in the session store separately, in three steps. It distills the actor's new conversations into durable facts with an LLM, merges and deduplicates those facts into topic-based entries under the same actor_id, then maintains that actor's memory by merging entries that overlap and splitting entries that have grown too large. Runs keep a checkpoint per actor and process everything since that actor's last successful run, so the first run processes their full history.

The docs are explicit about the blast radius. Maintenance applies to all of an actor's memory entries, including entries the agent saved directly, and a pipeline can rewrite, merge, split or delete any entry under an actor_id it processes.

That is the behaviour I'd plan around. A wrong fact that arrives as its own entry is a wrong entry you can find and delete. A wrong fact that survives a merge becomes a clause inside a longer, well-formed entry about a topic, sitting beside four true clauses, and the docs don't say which session_id the merged entry keeps. Provenance thins out exactly where you most need it, and it thins out silently, as a scheduled job doing what it was designed to do.

So we'd set session_id on every write and treat the optional field as required. It is the only thing that ties an entry back to the conversation that produced it, and the only way to answer "where did this come from" after a maintenance pass has reshaped the store.

Controls between a conversation and durable memory

The guidance in the docs I'd enforce hardest is about identity. Set the actor_id in trusted application code from the verified end-user identity, and never let the model choose whose memory to read or write. That is the same identity principle as in LakeWright.NET: the application resolves identity, not the model. If the model picks the actor, there is no boundary, only a suggestion.

Above that, we'd route every write through a governed function instead of letting the agent call the store directly, since the memory store sits on Lakebase storage and accepts whatever reaches it. Agents call tools, and the tools they call are governed as Unity Catalog functions, MCP services and connections under Unity Gateway, with service policies that control how each request and response proceeds based on its content and on who is calling. A save_memory wrapper gives you one place to reject content that reads as an instruction instead of a fact, to stamp a capture date into the text, and to keep anything about pricing, policy or entitlement out of a path the distillation step will later rewrite. One chokepoint is worth more than careful prompting, because the prompt is advisory and the function is not.

We'd also partition by trust using path. Paths are filesystem-like, so /preferences/ for what a user said about themselves and /policy/ for anything the business owns gives you a prefix you can list, review and expire on separate schedules. Memory belongs to what an agent learns about a person; the facts a business owns belong in governed tables.

Then rate memory the way you would rate retrieval. MLflow's built-in judges fit this shape of question without new tooling: RetrievalRelevance for whether retrieved items are relevant to the request, RetrievalGroundedness for whether the response is grounded in what came back, and RetrievalSufficiency for whether the retrieved context contains the facts you expected, which needs ground-truth expectations. All three require traces with at least one span marked span_type="RETRIEVER" (MLflow RAG judges). Trace the memory search as a retriever span and those judges apply to memory as they stand.

Agent proposes a memory

Direct write to memory store

Governed save_memory function

actor_id from verified user identity

Checks on content and path prefix

Memory store on Lakebase

MLflow judges score memory retrieval

The write path we would build, with identity set in application code and every memory write passing through a governed function before it reaches the store.

One thing that evaluation habit misses is that a set built only from true expectations measures recall. The question that decides whether memory is safe to run is whether a wrong entry, once written, still steers an answer three conversations later. So we'd keep a held-out set of facts we know to be false, write them in deliberately on a test actor, and check what the agent says afterwards.

Where we'd use it, and where we wouldn't

We'd reach for managed memory where the thing being remembered is a preference, a convention or a working style, like how someone wants results formatted, which dialect of the business they speak, or what project they're in the middle of. Databricks manages the storage, indexing and search, and it works with agents built on any framework. During the preview you're billed for the underlying Lakebase instance that stores the entries, with no additional charge for agent memory itself and pricing subject to change as the preview progresses. Pipeline runs bill as serverless general compute for the job plus Foundation Model Serving for the model calls, and the pipeline reaches its distillation model through Unity Gateway, so the model choice is governed like any other.

We wouldn't put anything an auditor would ask about into a free-text memory entry. Rates, eligibility, credit policy and clinical rules belong in tables with lineage, read through a governed tool on every request. A memory entry isn't a versioned table you can diff, and after a maintenance pass it may not even carry the session it came from.

Agent memory and memory pipelines are both in Beta, and the docs say APIs and behavior can change. That's workable for a pilot behind a feature flag, with one actor population and somebody reviewing entries. It isn't workable yet for an agent that acts without a human in the loop, and the first run is the reason. It processes an actor's entire conversation history, so whatever was wrong across months of chat arrives consolidated, in well-written sentences, on day one.

Where to start a pilot

If you're piloting agent memory, start with the smallest thing that stays useful. Populate session_id on every write even though it's optional, and decide your path prefixes before the store holds anything, because both get much harder once a maintenance pass has merged entries you can no longer trace. Put a save_memory function in front of the store after that, so there's one place to govern what a durable fact has to prove.

Frequently asked questions

What is agent memory poisoning?

Agent memory poisoning is an attack in which adversarial content is written into an agent's long-term memory, so a single write influences the agent's behaviour in later, unrelated conversations. A systematic study of the problem identifies four memory write channels and nine structural vulnerabilities behind them, and finds that existing prompt injection defenses do not cover memory poisoning (Dash et al.).

How does a Databricks memory pipeline decide what to remember?

It reads an actor's new conversations, uses an LLM to extract durable facts, merges them into topic-based entries under the same actor_id, then reorganizes that actor's memory by combining overlapping entries and splitting oversized ones. It is designed to keep explicit facts, expressed preferences, corrections and durable domain knowledge, and to skip live values that change over time, details the agent can look up with its tools, and secrets.

Can a memory pipeline change entries my agent wrote itself?

Yes. The Databricks documentation states that maintenance applies to all of an actor's memory entries, including entries your agent saved directly, and that a pipeline can rewrite, merge, split or delete any entry under an actor_id it processes.

How do you test what an agent remembers?

Trace the memory lookup as a retriever span and score it with MLflow's RAG judges, which measure whether retrieved items are relevant, whether the response is grounded in them, and whether they contain the facts you expected. All three require traces with a span marked span_type="RETRIEVER", and the sufficiency judge additionally needs ground-truth expectations.