2.04

2.04

Governing a long-term memory store

Apply the restatement gate and name the reading it would wrongly drop, supersede history while deleting junk, answer current and historical questions under the memory doctrine, and say what a deletion leaves behind.

Time
~38 min
Builds on
agent 1.06 context window 1.01 syndicate 2.01

Objectives

  • Identify the four doctrine rules: cite provenance, supersede corrections, expose contradictions, state silence

store hygiene deduplicates by meaning and supersedes by status

The memory lifecycle we have outlined has a specific failure mode that usually remains hidden until a system handles real traffic. Consider a production assistant whose long-term memory grew to 43 rows. A close audit revealed that those 43 rows represented only roughly seven distinct underlying facts. A single account balance had been recorded six different ways, a user preference appeared across seven entries, and thirteen separate summaries narrated the exact same session, each appending minor variations to the previous version. Because our retrieval pipeline evaluates a fixed shortlist (ten candidates in this implementation) those redundant restatements quickly crowded out the distinct records the user actually needed.

Why did the database degrade this way when every component appeared to be functioning correctly? Two design choices interacted unexpectedly. First, because an agent is stateless between user requests, the pipeline ran a distillation pass after every completed turn, re-reading the entire transcript from the beginning. A conversation spanning twenty turns triggered twenty sequential distillation passes over an ever-expanding dialogue. Second, the original deduplication check used an exact byte-for-byte string comparison. Because the distiller is an autoregressive model sampling tokens non-deterministically, each pass phrased its summary slightly differently. Every restatement of that balance bypassed the exact-match filter because its wording was new.

Resolving this requires two complementary safeguards:

Ingest incrementally. The processing pipeline maintains a persistent high-water mark for every session, recording the exact turn reached during the previous distillation pass. Subsequent passes process only the new turns added since that mark. Storing this cursor in memory rather than in a dedicated database table represents an intentional design trade-off: following a process restart, an active session is re-evaluated once from the beginning, and our second safeguard cleanly handles any duplicates that single re-read creates.

Deduplicate by meaning. Before inserting any new record, the system queries the database using the same cosine-similarity search recall uses. In our specimen, the runtime compares the candidate against the five nearest stored rows for that user key. If any active record carrying the same category tag scores at or above 0.93 cosine similarity, the system classifies the candidate as a restatement and drops it. This 0.93 threshold is specific to the 768-dimensional embedding model used here; different embedding models distribute semantic scores across different curves, so you should calibrate this threshold against your own domain data rather than adopting it blindly. This is the exact gate that intercepted the 0.97 restatement in the recorded run the previous module followed. Two structural constraints reinforce this check: category tags must match exactly (ensuring a narrative session summary containing a medication reference does not suppress the clinical dosage record itself), and superseded historical records never block new incoming facts.

That one similarity number is all the specimen’s rule reads. The embedder transforms the entire structured record line into a single vector, so the entity, the numeric value, the unit, and the effective date are tokens inside one string, and the probe returns one cosine score for the whole line. No step in the rule compares the dose, the units, or the date as separate fields before dropping a candidate. Two records that differ only in a dose or a date are judged on that single score, and the trend material described earlier is the sharpest case: an unchanged blood pressure reading or an unchanged dose recorded on a new date is exactly the record this rule can drop, and the trend is visible only if each reading survives with its own timestamp.

Calibrating this deduplication threshold is a judgment you make from your own data. Setting the threshold too high permits restatements to slip through; setting it too low risks suppressing genuinely distinct observations. When evaluating edge cases (or when a similarity query encounters a transient database error) consider the asymmetric cost of the two potential failures. A duplicate entry consumes one candidate slot out of ten on a retrieval shortlist. A dropped fact, by contrast, permanently loses critical information provided by the user without any visible warning. Because the consequences are uneven, our design policy is unambiguous:

A duplicate is cheap; a lost fact is not. A duplicate costs a shortlist slot; a lost fact costs the user something they said. Every doubt resolves toward storing, and a deduplication check that fails stores the record anyway.

The same asymmetry sets the stricter rule a clinical store needs, and in high-stakes environments you implement deterministic, field-level deduplication before dropping a record:

  1. Extract the structured attributes of the candidate and of each near match: the indexed entity, the exact numeric value, the unit of measurement, and the effective date.
  2. Drop a candidate as a duplicate only when the entity, value, unit, and date all match an active row.
  3. If the entity matches but the value, unit, or date differs, store the record as a new reading. It supersedes the earlier record only through an explicit supersession, written when a verified change occurs.
  4. Hold an ambiguous candidate, one that scores high on similarity but whose fields cannot be compared or only partly match, in a review queue, where a reviewer decides whether it restates an existing record before anything is dropped.

Our specimen executes the whole-line cosine check with exact tag matching and runs none of these four steps; none of its memory settings configures a field comparison or a review queue. You add them when you build a clinical store.

Tell the distiller what your domain keeps. While every agent team using long-term memory shares a core distillation prompt, the criteria for what constitutes a durable fact vary across domains. The team definition accommodates this through memory_extraction_rules, a per-team configuration field appended to the base distillation prompt at runtime. In practice, this prompt extension requires only two guidelines: never retain ephemeral values that decay automatically (such as live market prices or temporary sensor readings that the system refreshes dynamically, which would only clutter the database), and always capture user commitments, decisions, and explicitly asserted preferences. A team that omits the field runs the shared base prompt exactly as written, and the rules in that prompt apply to any domain.

Clean up by deleting, not by retiring. As we will see in our memory doctrine, marking records as superseded is the correct mechanism for handling facts that were historically accurate; an initial prescription of 25 mg was genuinely true at the time, and our audit trail should preserve that history. Redundant conversational junk requires a different approach. A duplicate restatement never represented useful information, and marking it superseded does not remove it from the retrieval pipeline: our initial similarity search filters by user ID and evaluates status flags only during subsequent re-ranking, so a retired duplicate still occupies one of the 24 candidate slots in the initial retrieval sweep, and the small boost re-ranking gives active records does not always keep it out of the final ten. To clean out genuine junk, the specimen framework includes a command-line maintenance utility, which scans a user’s records, clusters probable duplicates, and permanently deletes selected entries. The utility runs in dry-run mode by default and isolates every deletion to the targeted user partition.

Make store maintenance a routine engineering practice: once a month, query a sample user partition and compare the total row count against the number of unique underlying facts. If redundant rows are multiplying, your retrieval shortlist is already suffering from degraded precision.

the memory doctrine prevents fabricated recollections

Operational doctrine expands alongside system capability. Whenever you grant an agent a new capability, you must define the explicit rules governing its use, because an unregulated power leads to unpredictable behavior. Memory represents an exceptionally sensitive capability: a fabricated recollection is far more persuasive and difficult to detect than a generic factual hallucination.

A formal memory doctrine establishes how recalled records must be cited, updated, and communicated when the database contains no matching entries.

<memory_doctrine>
1. EVERY FACT REQUIRES PROVENANCE: Every stored record carries a date, source,
   and exact value with units. When stating a remembered fact, cite its source
   explicitly ("According to your discharge summary dated 2026-07-03…").
2. CORRECTIONS SUPERSEDE ONCE VERIFIED: When an order or a clinician's
   document confirms a change, mark the old record `superseded` and store
   the new value as `active`. When the user reports a change, store the
   report with its source beside the existing order, show both, and
   supersede only once the change is confirmed. Never quote a superseded
   value as current.
3. EXPOSE CONTRADICTIONS: If two stored records conflict, state the conflict
   and ask the user. Never silently guess which record is correct.
4. STATE SILENCE CLEARLY: If the record holds no data for a request, say so.
   Never invent a historical fact.
</memory_doctrine>

Read that as four answers to four questions the model will otherwise answer by guessing. Where did this come from? Say so, with the date. Which of two values is current? The one marked active and confirmed by an order, and say the other was superseded; a change the user reported stays a reported change, with both values shown, until it is confirmed. What if two records disagree? Say that they do, and ask. What if there is nothing there? Say that there is nothing there.

Rule 4 carries critical clinical weight. Confabulation is the mechanism underneath it: asked about information it does not hold, a language model can produce fluent, plausible text that mimics the structure of an accurate answer. Anthropic’s study of Claude 3.5 Haiku found a pathway that is on by default and makes the model state that it has insufficient information; a feature that recognizes known entities inhibits it, and when that feature misfires, it suppresses the default and the model fabricates an answer. When historical memory is involved, the risk escalates dramatically, because a fabricated answer mimics a specific, dated interaction the patient assumes took place. A user has no reliable way to distinguish an authentic recollection from a plausible hallucination. The doctrine must therefore make stating that “the record is silent” the mandatory default response, which is the grounding requirement applied to the record rather than to a document.

A silent store is reported as silent. A memory system must never invent a historical record. If the store holds no matching data, the agent states that the record is silent.

Rule 2 dictates how database updates execute under the hood. When a patient’s dosage increases from 25 mg to 50 mg, the original database row is not erased. Instead, its status field is updated to superseded, and the new 50 mg entry is inserted as active. The original record remains available for historical auditing. In the specimen, that update happens only when the distiller writes a correction quoting the old claim and a similarity lookup on that quote finds the old row; when either step misses, the old row stays active.

Answering “what is the patient taking right now?” reliably requires an explicit current-state retrieval path: query only the records where status = 'active' for the entity the question names, select the latest effective date, and check the selected record’s status and provenance before the reply calls it current. Historical questions such as “what dosage was prescribed in March?” take a separate historical query path that retrieves records of every status, ordered by date, with each superseded entry labelled, so the agent can answer them without presenting a past treatment as an active order.

Our specimen runs one similarity search over all of a user’s records for both kinds of question. The runtime adds a small boost (0.05) to active records and relabels any superseded record that reaches the top ten as SUPERSEDED by a later correction. Because the database query does not filter on status, an outdated record can still take a slot in the window. Keeping the agent from presenting that figure as current rests on the model following the prompted doctrine as it reads the relabelled text, and a supersession that never fired leaves the old row marked active, with no label for the doctrine to act on.

This doctrine also enforces a crucial clinical boundary. Once an agent can retrieve a clinician’s specific threshold, users will inevitably ask it to supply thresholds that no physician ever established. Because the generated responses look identical on the surface, our system boundary must govern the provenance of all clinical metrics:

Numbers belong to their clinician. A numeric action threshold, a hold-the-dose cutoff, a when-to-call vital sign, may appear in the agent’s output only as a quote from this patient’s record, attributed to the clinician or document that set it, with its date. An agent supplying a “typical” cutoff from general medical knowledge has written a prescription, whatever the surrounding language says.

A session reconstructed from a run of the specimen shows its doctrine at work in three specific behaviors: the agent cites recalled facts with their explicit sources, attributes clinical alert cutoffs directly to the attending cardiologist rather than claiming universal standards, and supersedes the previous dosage on the user’s report of a change while retaining it in audit history.

Two technical details in that execution demonstrate our architectural principles. First, recall returned eight records ambiently via preload_memory before the agent processed the message, allowing the initial response to cite exact dates without executing an extra tool call. Second, the medication adjustment shows the specimen’s own correction rule at the database level: the distiller wrote a correction with the source user, per cardiologist, the 25 mg record from the discharge orders was marked superseded rather than deleted, and the 50 mg record was inserted as active, ensuring the system can still reconstruct what the patient was taking in early July. What the store recorded is a user’s report of a change, and it superseded an order document on that report, because the specimen’s prompt supersedes on the user’s word and no step in the engine reads a record’s source before superseding. A clinical store following Rule 2 above holds that report as a reported change awaiting confirmation, with both values visible and both sources named, and supersedes the 25 mg order only when an order or a clinician’s document confirms the 50 mg dose.

long-term memory is a regulated PII database

We must be completely direct about the regulatory and security implications here, because it is dangerously easy to build a functional memory store without recognizing what it contains.

A long-term memory store systematically collects medical diagnoses, legal names, chronological logs, and personal habits, explicitly linked to individual user IDs. By every legal and ethical standard, this constitutes a sensitive personal data store, and it assumes that classification the moment it begins capturing information reliably.

One safety boundary sits at the prompt input layer, where you sanitize credentials and sensitive variables before any text is transmitted to an API. Long-term memory moves that boundary deeper inside your application architecture, because the system itself now determines what data to fetch from storage and inject into the prompt. The same engineering rigor must protect the database itself. You must implement automated retention schedules with enforced expiration dates, programmatic deletion workflows that permanently purge records rather than simply masking them, and strict tenant isolation enforced at the database query layer so that no retrieval sweep can leak data across user boundaries. These are fundamental operational requirements that must be committed alongside your initial memory implementation, not deferred as future compliance tasks.

When you design memory erasure, audit exactly which stores and logs keep information after a deletion; in our specimen framework, each deletion path reaches a different set. The maintenance utility’s delete, for one record or for a user’s whole silo, removes rows from adk_memory_facts only, and the conversation transcripts in adk_sessions stay until they are cleared separately; an idle session expires after seven days and is removed when a separate session-pruning command runs. The server endpoint DELETE /memory erases, in one transaction, the memory facts, sessions, turns, spans, payloads, verdicts, labels, tasks, memory markers, task tools and credentials for the server’s memory namespace (?all=1 covers every namespace). After that erase, the append-only audit table keeps one row for it, holding the caller’s name, a SHA-256 prefix of the scope key, the source IP and the per-store counts, with no conversation content; usage counters hold numbers only and are not erased; and the server’s standard output log has printed the scope key on erase and user keys during ingestion and deletion, kept wherever your deployment keeps that log. For that reason, build user keys from an opaque account id, never an email address or a name.

what you can do with this today

You can use this four-stage memory lifecycle as a systematic diagnostic framework whenever an agent mishandles historical information. When an agent misstates or forgets a fact, step through the pipeline to locate exactly where the breakdown occurred:

Was it ever stored? If the information never reached the database, either the distillation prompt filtered it out, or the observation fell outside the categories your store retains. Compare the original session transcript against the rows in adk_memory_facts. If a durable fact was shared but never written to disk, refine your distillation prompt or adjust the memory_extraction_rules for that team.

Was it stored in a form that can be read cold? Ensure every record includes an absolute date, explicit provenance, exact numerical values with units, and a valid status flag. A record that relies on conversational context from the day it was created will inevitably be misinterpreted when retrieved in isolation weeks later. Ensure distilled facts stand completely on their own.

Was it found? If a stored fact failed to reach the context window during a query, the failure happened during recall. Check whether the shortlist was saturated with redundant restatements, verify whether the user’s vocabulary aligned with indexed category keys, and confirm that the agent had access to its deliberate retrieval tool for complex searches. Establish your data retention and pruning rules early: decide what gets dropped, what gets superseded, and what gets purged. Memory systems that only accumulate data inevitably become slower and less precise. Mark updated history as superseded, and permanently delete conversational junk so invalid rows do not waste candidate retrieval slots.

Was it in the window and still misstated? If the relevant record was present in the prompt but the output was inaccurate, the failure lies in doctrine enforcement. Quoting an outdated measurement as active, inventing details when the store is silent, or stating a clinical threshold without attributing it to a specific provider are direct doctrine violations, and each is countable against the transcript.

Treat long-term memory as a sensitive data store from your very first commit. Building secure deletion, lifecycle retention, and tenant isolation into your initial design is straightforward; attempting to retrofit them onto an existing production store is costly and difficult.

Before a memory-enabled agent holds a real patient’s record, seed a test silo with the values already on this page and run five cases against it:

  1. Store metoprolol tartrate 25 mg twice daily dated 2026-07-03, then a confirmed change to 50 mg dated 2026-07-10, and look for the current-state path to return only the 50 mg record while the historical path returns both.
  2. Store a blood pressure reading of 148/92 mmHg, then the same reading on a later date, and look for both readings to survive deduplication so the trend stays visible.
  3. Store the discharge order for 25 mg, then a user’s report that the dose is now 50 mg, and look for the agent to show both values with their sources and ask which is right, with neither silently overwriting the other.
  4. Ask what dosage was prescribed on 2026-07-03, and look for the 25 mg record to come back labelled superseded, with its date and source.
  5. Seed records under two different user keys, run recall as each user, and look for every returned record to carry the key of the user who asked.

Against the specimen, cases 1 to 3 exercise rules it does not run; record what it does, and treat each gap as work for your own store.

Review the complete memory lifecycle summarized on the reference card below:

1. SESSION STATE            adk_sessions
   The raw transcript, turn by turn. Survives a restart; resumes by
   session id. Judges nothing.

2. DISTILL                  at session end, incrementally
   Read only the turns since the high-water mark.
   Keep: dated events with values · explicit preferences ·
         core identity · trend material.  Drop everything else.
   Each kept fact rewritten to stand alone:
         absolute date · provenance · exact value with units ·
         status · index keys.

3. DEDUPLICATE              before insert
   Specimen: probe the store by meaning. Same category, active,
   and at or above 0.93 similarity → a restatement, dropped.
   A clinical store adds: drop only when entity, value, units
   and effective date all match; ambiguous → held for review.
   Any doubt, any probe error → store it; a lost fact costs more.

4. STORE                    adk_memory_facts
   The embedding (768 numbers) beside the text and the indexed
   metadata columns. Corrections supersede; junk is deleted.

5. RECALL                   before the model sees the question
   Specimen: similarity search → 24 candidates → re-ranked by
   keys, dates and status → the top ten. Ambient via preload_memory;
   deliberate via load_memory.
   A clinical store adds: a current-state query (active rows only,
   status and provenance checked) beside a historical query.
   Matching facts land in the context window as text, like any fact.

6. ANSWER                   under the memory doctrine
   Cite the source and date. Never quote a superseded value as
   current. Expose contradictions. If the store is silent, say so
   and invent nothing. Numbers belong to their clinician.

In the next lesson, we will expand our agent’s operational boundaries beyond its internal database, connecting it to live third-party services using the Model Context Protocol (MCP) and establishing the safety controls required when an agent acts directly on external systems. You will learn how to equip an agent with external tools dynamically while ensuring critical boundary checks remain strictly outside the model. Continue to extending reach with MCP to proceed.

End of module 2.04

You have reached the end of this module. Marking it finished is your own call, saved in this browser, and you can undo it at any time.

0 of 34 modules marked finished.