The MentisDB Agent Memory Cookbook

Patterns and recipes for building AI agents that remember

1.6 Preference Learning

Preference learning is not just saving "the user likes X." Real preferences change, conflict, and depend on context. Store them as explicit PreferenceUpdate thoughts, attach concepts that describe the context, and supersede older preferences when the user gives a newer instruction.

The Pattern

use mentisdb::{
    BinaryStorageAdapter, MentisDb, RankedSearchQuery, ThoughtInput,
    ThoughtQuery, ThoughtRelation, ThoughtRelationKind, ThoughtType,
};

fn learn_and_update_preferences() -> io::Result<()> {
    let dir = tempfile::tempdir()?;
    let adapter = BinaryStorageAdapter::for_chain_key(
        dir.path(),
        "cookbook-preferences",
    );
    let mut chain = MentisDb::open_with_storage(Box::new(adapter))?;

    chain.upsert_agent(
        "assistant",
        Some("Assistant"),
        Some("dev-team"),
        Some("Learns user preferences from direct feedback"),
        None,
    )?;

    let first_preference = chain.append_thought(
        "assistant",
        ThoughtInput::new(
            ThoughtType::PreferenceUpdate,
            "For code review, the user prefers concise findings before summaries.",
        )
        .with_concepts(["review-style", "code-review"])
        .with_tags(["preference", "user"])
        .with_importance(0.8)
        .with_confidence(0.9),
    )?.id;

    chain.append_thought(
        "assistant",
        ThoughtInput::new(
            ThoughtType::PreferenceUpdate,
            "For high-risk code review, the user now wants severity, file references, and test gaps before any summary.",
        )
        .with_relations(vec![ThoughtRelation::new(
            ThoughtRelationKind::Supersedes,
            first_preference,
        )])
        .with_concepts(["review-style", "code-review", "risk"])
        .with_tags(["preference", "user", "current"])
        .with_importance(0.9)
        .with_confidence(0.95),
    )?;

    let query = RankedSearchQuery::new()
        .with_text("How should I present a high-risk code review?")
        .with_filter(
            ThoughtQuery::new()
                .with_types(vec![ThoughtType::PreferenceUpdate])
                .with_concepts_any(["review-style"]),
        )
        .with_limit(3);

    let results = chain.query_ranked(&query);
    assert!(!results.hits.is_empty());
    assert!(results.hits[0].thought.content.contains("high-risk"));
    Ok(())
}
Do not delete old preferences. Superseding preserves auditability while letting retrieval favor the newer, narrower instruction.

Conflict Handling

If two preferences both apply, prefer the one with the narrower concept set and higher importance. If the conflict is real, ask the user and store the answer as a new PreferenceUpdate that supersedes the ambiguous one.