How do you handle preference drift over time?

Short answer: Absorb gradual preference change by rewriting the live preference, using short-window vs long-trend signals so noise does not stack conflicting tastes.

Drift differs from a hard one-shot contradiction. Engram transforms should rewrite preferences, not pile paraphrases. Log product signals, compare recent vs long baseline, and only update past a threshold. Separate drift from persona non-compliance when memory is right but the model ignores it. Guard against oscillation with confirmation and review.

Preference drift is change without a single dramatic correction. Reconsolidation handles the sharp “actually, use grade 4” moment. Drift is quieter. Over weeks a user asks for shorter replies, then denser ones, then shorter again with examples. Or a tea buyer slowly moves from floral to roasted profiles without ever saying “forget jasmine.” Naive memory keeps every snapshot. The agent then retrieves conflicting tastes and averages them into nonsense. Weaviate Engram’s transform path is designed for preference changes over time. New evidence should rewrite the live preference, not stack paraphrases. Your application still needs signals that decide when gradual movement is real drift versus noise.

This chapter distinguishes drift from contradiction, shows how Engram rewrite absorbs preference updates, outlines short-window versus long-trend detectors you can run outside the store, and walks through a scoped drift update. Scheduled maintenance jobs come next. Drift policy is what those jobs enforce for personalization topics.

How is preference drift different from a hard contradiction?

After reconsolidation, you already know how to handle an explicit flip. Drift is softer. The old preference is not suddenly false in one sentence. Behavior tips. Orders, clicks, and mild phrasing accumulate. “Lately I’ve been into roasted oolongs” sits beside an older “loves jasmine green.” Both can be partially true during a transition. Treating every soft hint as a hard supersession creates thrash. Ignoring soft hints leaves the agent stuck in last season’s taste.

Engram documentation groups this under reconciliation. Facts change. Preferences evolve. Transform steps query related memories and choose rewrite, keep, or delete. Topic descriptions should tell the pipeline that taste and format preferences are mutable. Otherwise extraction may treat each hint as a brand-new eternal fact.

Also watch a second failure mode. Iterative summarization can intensify mild preferences into absolute ones. “Likes mild spice” becomes “loves very spicy” after enough rewrite passes. Drift handling must preserve intensity, not only category. Keep the live memory specific. Anchor high-stakes nuances in atomic UserKnowledge rather than only in rolling summaries.

How should Engram absorb a drifting preference on the write path?

Once you decide an update is warranted, prefer write-path maintenance. Send the new preference through memories.add in the same user_id and property scope. Transform retrieves the prior preference and can rewrite it with a short history clause, then drop the duplicate extract. That matches the promotion example in Engram’s own pipeline stories. The live object stays singular. Search stops returning two equal winners.

Tune topic text so preference topics expect change. Bounded profile topics help when you want one canonical preference blob per user. Unbounded topics need stronger transform instructions so near-paraphrases merge. Always scope by user. One customer’s drift must never rewrite another’s.

Do not wait for a nightly job to record a clear stated change. If the user says “I’ve switched to roasted oolong as my default,” that is an immediate add. Drift detectors exist for the cases without a clean sentence.

How can you detect gradual drift before the user spells it out?

Knowing Engram can rewrite, you still need a when. Preference-aware update research often compares a short sliding window with a longer exponential moving average. When short-term behavior diverges from the long-term baseline past a threshold, trigger a memory update. That dual view catches both sudden shifts and slow slides while filtering one-off noise.

In practice, log lightweight preference signals your product already has. Chosen SKUs, rejected suggestions, requested response length, tone markers. Score them into a small vector per day. Compare the last two weeks with the last three months. If the gap is large for a dimension you care about, draft a candidate preference string and send it to Engram. If the gap is small, do nothing. Controllable thresholds beat rewriting on every session.

Behavioral drift is not the same as persona non-compliance. Sometimes the memory is correct and the model ignores it. Measure both. Drift detectors update memory. Separate monitors catch when retrieved preferences are present but not followed.

What does a scoped drift update look like in code?

Imagine a tea desk agent on tea-blending-cupping-desk-6. Purchase signals show a move from jasmine greens toward roasted oolongs. The job rewrites the live taste memory only after the short window disagrees with the long baseline.

import os
from datetime import datetime, timezone
from engram import EngramClient, HybridRetrieval

client = EngramClient(api_key=os.environ["ENGRAM_API_KEY"])
buyer = "buyer-iris-quen"
desk = {"desk_id": "tea-blending-cupping-desk-6"}

# Toy scores: higher means more roasted-leaning purchases that day.
long_term = [0.2, 0.25, 0.2, 0.3, 0.22, 0.28, 0.25]
short_term = [0.55, 0.6, 0.7, 0.65]

def mean(xs):
    return sum(xs) / max(len(xs), 1)

def drift_detected(short_vals, long_vals, delta: float = 0.25) -> bool:
    return abs(mean(short_vals) - mean(long_vals)) >= delta

def current_preference():
    hits = client.memories.search(
        "preferred tea profile cupping",
        user_id=buyer,
        group="personalization",
        properties=desk,
        retrieval_config=HybridRetrieval(limit=5),
    )
    return hits[0] if hits else None

def apply_drift_update():
    if not drift_detected(short_term, long_term):
        return "no-drift"

    prior = current_preference()
    stamp = datetime.now(timezone.utc).date().isoformat()
    if prior and "roasted oolong" in prior.content.lower():
        return "already-current"

    correction = (
        f"As of {stamp}, on tea-blending-cupping-desk-6 the preferred everyday "
        f"profile is roasted oolong. Earlier preference leaned floral jasmine greens."
    )
    run = client.memories.add(
        correction,
        user_id=buyer,
        group="personalization",
        properties=desk,
    )
    client.runs.wait(run.run_id)

    live = client.memories.search(
        "preferred tea profile cupping",
        user_id=buyer,
        group="personalization",
        properties=desk,
        retrieval_config=HybridRetrieval(limit=5),
    )
    return [m.content for m in live]

# Seed an older preference, then absorb drift.
client.runs.wait(
    client.memories.add(
        "On tea-blending-cupping-desk-6, preferred everyday profile is floral jasmine green.",
        user_id=buyer,
        group="personalization",
        properties=desk,
    ).run_id
)
print(apply_drift_update())

The numeric detector is deliberately plain. Replace the toy scores with real purchase or feedback features. Keep Engram as the system of record for the preference text. Keep the detector in application code where thresholds are easy to audit.

What guardrails keep drift updates from oscillating?

Require minimum evidence in the short window. Two odd orders should not rewrite a year of taste. Cooldown after an update so the next week of mixed signals does not flip back immediately. Preserve a short “previously” clause so agents can explain the change. For safety or accessibility preferences, demand explicit confirmation instead of silent behavioral inference.

Review false positives. If users complain that the agent “decided” a new taste for them, raise the delta or require one confirming utterance. Review false negatives. If agents keep recommending jasmine after a month of roasted purchases, lower the delta or improve feature quality.

Drift policy is continuous. Enforcing it at scale means scheduled cleanup and merge jobs that scan scopes, apply detectors, and repair leftover duplicates. That operational layer is the next piece of memory maintenance.

Our next chapter, What are memory maintenance jobs?, turns these policies into recurring work that keeps the store coherent without waiting for the next chat turn.