Short answer: Pruning is ongoing hygiene that removes aged notes and leftovers after a newer fact should have won.
Soft forgetting demotes; archive and delete handle special cases. Engram transforms and bounded topics catch many write-path merges, but seasonal notes and near-duplicates need a prune loop. Prefer structured transforms; gate risky deletes; archive first when unsure. Measure duplicate and conflict rates in top-k. Exempt safety-critical topics from simple TTLs.
Pruning is the quiet maintenance that keeps agent memory coherent. Soft forgetting only demotes stale facts in ranking. Archival and hard deletion handle special cases. Pruning is the ongoing job of removing memories that no longer earn their place, and of clearing leftovers after a newer fact should have won. Without it, hybrid search returns yesterday and today with equal confidence. The agent then serves superseded advice as if both were still true.
Weaviate Engram already does part of this work on the write path. Transform steps can rewrite an older memory and delete a redundant new extract when preferences change. Bounded topics collapse to one object per scope. Your application still needs a prune loop for what the pipeline does not catch: aged seasonal notes, near-duplicate paraphrases, and stale lines that survive beside a correction. Engram search, get, and delete give you the tools. Policy decides what counts as stale or superseded.
What counts as stale, and what counts as superseded?
Stale means the fact is no longer useful for current work, even if it was never formally contradicted. A one-week shipping window from last quarter is stale. A temporary workaround for a fixed bug is stale. Recency, topic class, and access frequency are common signals. Engram exposes created_at and updated_at on every memory so age-based rules are easy to apply after list or search.
Superseded means a newer memory should replace an older one on the same subject. The user changed preferred oil. The API endpoint moved. The adhesive brand was corrected. Research on evolving knowledge shows that embedding similarity alone often cannot tell a contradiction from a paraphrase. Both can score high. Pruning therefore needs an explicit rule, not hope that ranking will pick the winner.
Engram’s transform path is the first supersession engine. When a promotion updates a job title, the pipeline may rewrite the prior role memory and drop the duplicate extract. Trust that path for ordinary conversational updates. Schedule application-side prune jobs for leftovers and for age-only retirement.
How should pruning interact with Engram’s pipeline?
Prefer write-time maintenance when you can. Send corrections through memories.add so transform can reconcile. Tune topic descriptions so extraction prefers stable canonical facts over chatty restatements. That reduces how often a nightly job must delete by hand.
Still run a scoped sweeper. List or search within a user_id and property scope. Cluster near-duplicates by shared keywords or by a cheap similarity check in your app. Keep the newest or the explicitly corrected wording. Delete the rest with memories.delete. Verify with a follow-up search that the stale phrasing is gone.
Be careful with aggressive rewrite loops. Studies of continual textual consolidation show that constant LLM rewriting can damage useful detail. Prefer Engram’s structured transform for routine merges. Use human or rule-gated deletes for prune candidates that look risky. When in doubt, archive first, then delete from the live group.
What does a practical prune job look like?
Imagine a weaving studio agent on loom-warp-tension-desk-7. Old tension setpoints linger after a reed change. A correction was written, but an older string still retrieves. The prune job finds both, keeps the corrected memory, and removes the superseded one.
import os
import re
from datetime import datetime, timezone, timedelta
from engram import EngramClient, HybridRetrieval
client = EngramClient(api_key=os.environ["ENGRAM_API_KEY"])
weaver = "weaver-june-park"
loom_scope = {"loom_id": "warp-desk-7"}
STALE_AFTER = timedelta(days=90)
def parse_ts(value: str) -> datetime:
return datetime.fromisoformat(value.replace("Z", "+00:00"))
def seed_and_correct():
client.memories.add(
"On loom-warp-tension-desk-7 keep warp tension at 28 cN for cotton 20/2.",
user_id=weaver,
group="personalization",
properties=loom_scope,
)
# Correction should ideally merge via transform; prune catches leftovers.
client.memories.add(
"Correction for loom-warp-tension-desk-7: after reed change use 24 cN, "
"not 28 cN, for cotton 20/2.",
user_id=weaver,
group="personalization",
properties=loom_scope,
)
def prune_scope(query: str = "warp tension cotton"):
now = datetime.now(timezone.utc)
hits = client.memories.search(
query,
user_id=weaver,
group="personalization",
properties=loom_scope,
retrieval_config=HybridRetrieval(limit=30),
)
deleted = []
# Supersession heuristic: if a correction exists, prune older absolute setpoints.
has_correction = any("correction" in m.content.lower() or "not 28" in m.content.lower() for m in hits)
for m in hits:
age = now - parse_ts(m.updated_at)
content = m.content.lower()
superseded = has_correction and re.search(r"\b28\s*cn\b", content) and "not 28" not in content
stale = age > STALE_AFTER and "cotton 20/2" in content and "24 cn" not in content
if superseded or stale:
client.memories.delete(m.id, user_id=weaver, group="personalization")
deleted.append(m.id)
remaining = client.memories.search(
query,
user_id=weaver,
group="personalization",
properties=loom_scope,
retrieval_config=HybridRetrieval(limit=10),
)
return deleted, remaining
seed_and_correct()
removed, kept = prune_scope()
print("pruned", removed)
for m in kept:
print(m.updated_at, m.content)
The heuristics are deliberately boring. Keyword and age rules are auditable. You can replace them later with richer subject-relation keys. Start with rules you can explain in an incident review.
How often should you prune, and what should you measure?
Run prune jobs on a schedule per high-churn scope, not on every chat turn. Nightly or weekly is enough for most personalization groups. Trigger an extra pass after bulk imports or after a known product change. Always prune inside a user and property boundary first. Global sweeps are how you delete the wrong tenant’s facts.
Measure stale-fact errors on held-out questions where the correct answer changed. Measure duplicate rate in top-k search. Measure how often the agent cites two conflicting setpoints in one reply. If those metrics fall after prune runs, the job is working. If useful rare facts disappear, widen exemptions for safety-critical topics.
Exempt with intention. Hard safety constraints may never age out on a simple TTL. Put them in a topic your sweeper skips, or require dual approval before delete. Pruning is hygiene. It is not a substitute for the contradiction detector you will need when two live memories both look current.
Our next chapter, How do you detect contradictions between old and new memories?, focuses on finding conflicting live facts before prune rules decide which one survives.