What are user profiles as bounded memory?

Short answer: A profile is one living Engram memory per user, enforced by a bounded topic scoped to user_id so updates rewrite in place.

Without bounds, near-duplicate about-the-user notes fight in search. Deterministic ids from topic and scope keep a single slot. Pair one bounded UserProfile with unbounded topics for accumulating likes. Gate writes and fetch the profile for identity context without flooding the prompt. That is the personalization contract for standing identity.

A user profile in agent memory should be one living document per person, not a pile of near-duplicate “about the user” notes that fight in hybrid search. Weaviate Engram gives that shape a first-class name: a bounded topic. When a topic is marked is_bounded and scoped by user_id, the pipeline keeps at most one memory for that scope. The memory id is derived deterministically from topic and scope, so later extractions update the same profile in place instead of minting rivals.

This chapter defines profiles as bounded memory constructs, contrasts them with unbounded preference facts, shows how to read and refresh a profile safely in an agent loop, and walks a lace bobbin-winding desk through a UserProfile topic beside finer preference topics. Next comes how preferences themselves drift and learn over time without shredding that profile contract.

Why does “one profile per user” need a bounded topic?

Personalization often starts with unbounded extraction: every session adds another memory that partially restates who the user is. Hybrid search then returns three overlapping bios with slightly different dietary rules or tool preferences. The model blends them, or picks the wrong vintage. Soft-forgetting helps, but the deeper fix is cardinality. A profile is canonical. It should have one slot per user.

Engram’s bounded topics encode that slot. Docs describe a UserProfile topic scoped by user_id as the pattern for one profile per user. Transform steps such as TransformWithContext and TransformAggregate honor the bound: if extraction would produce multiple profile-shaped facts for the same scope, consolidation collapses them into the single memory. That is different from hoping your application dedupes after the fact.

Use the profile for stable, cross-session identity context: how the user likes to be addressed, standing constraints, high-level goals, communication style. Keep atomic, accumulating facts—individual product likes, one-off project notes—on unbounded topics when you need history and fine retrieval. The profile is the always-on sketch. Unbounded topics are the expandable filing cabinet. Mixing those jobs into one unbounded UserKnowledge soup is how personalization feels both forgetful and overcrowded at once.

How should agents read and write a bounded profile?

Because the id is deterministic from topic and scope, agents can treat the profile as a known object. After the first successful commit, prefer memories.get with FetchRetrieval (or search restricted to topics: ["UserProfile"] with a tight limit) so you are not re-ranking the whole personalization group for “who is this user.” Pinning the profile into working memory at session start is a common pattern: one fetch, then rely on it until the sitting ends unless the user clearly changes a standing fact.

Writes still go through memories.add with conversation or string input and the required user_id. The pipeline updates the same bounded memory. Your write gate should be stricter for profile updates than for unbounded preferences. Standing identity facts belong. Ephemeral task state does not. If the user says “for this job only, use metric units,” that is session or job-scoped memory—not a profile rewrite that will haunt every future session.

When the user contradicts the profile—“I no longer want formal tone”—the bounded update should replace the old clause inside the single memory. That is profile reconsolidation. Leaving the old tone instruction in an unbounded twin while the profile also changes is how agents sound inconsistent. Prefer updating the bounded profile and superseding or deleting the stray unbounded clone when you find one.

How do profiles fit beside other topics and planes?

A healthy personalization group often mixes one bounded UserProfile with several unbounded topics such as food preferences, tool preferences, or project affinities. Topic descriptions keep extraction magnets distinct: the profile description should ask for durable identity and standing preferences summary, not every transient like. Search can omit topic filters when you want a blended personalization pack, or request only UserProfile when the turn needs identity context without flooding the prompt with every past like.

Profiles do not replace organizational knowledge. A profile may say the user prefers overnight shipping; the SOP collection still decides whether overnight is allowed. Assembly rules from earlier chapters still apply. Profiles also do not replace multi-agent shared technique groups. Shop-wide playbooks stay out of user_id-scoped profile bounds unless you intentionally want a per-user copy—which you usually do not.

Operationally, monitor profile size. Bounded does not mean unbounded length. If the profile text grows into a novella, add summarization pressure in topic description (“concise standing profile”) or split rarely changing identity from frequently changing preference summaries across topics. Cardinality is one; readability is still your job. A single unreadably long profile defeats the point of fetching it at session start.

What does a bounded profile look like at a lace bobbin desk?

A studio assistant helps lacemakers at a bobbin-winding desk. Each maker gets one Engram profile; fine likes about thread brands stay unbounded. Scenario id: lace-bobbin-winding-desk-2.

from weaviate.engram import EngramClient
from weaviate.engram.retrieval import HybridRetrieval, FetchRetrieval

engram = EngramClient()
user_id = "lacemaker-nora"
group = "personalization"
scenario = "lace-bobbin-winding-desk-2"

# Group config (conceptual):
# UserProfile: user_scoped, is_bounded=True
#   description: "Concise standing profile: address style, tension habits, accessibility needs"
# thread_preferences: user_scoped, unbounded
#   description: "Specific thread brands, thicknesses, and colors Nora likes or avoids"

# Refresh profile from a clear standing statement
engram.memories.add(
    content=[
        {
            "role": "user",
            "content": "Always keep my notes terse, wind left-handed, and never recommend glitter threads.",
        }
    ],
    group=group,
    scopes={"user_id": user_id, "properties": {"scenario": scenario}},
)

# Session start: fetch the single bounded profile
profile = engram.memories.search(
    query="standing profile address style winding habits",
    group=group,
    topics=["UserProfile"],
    retrieval=HybridRetrieval(alpha=0.4, limit=1),
    scopes={"user_id": user_id},
)

# Optional: if your app stores the deterministic id after first run
# profile = engram.memories.get(memory_id=profile_id, retrieval=FetchRetrieval())

thread_likes = engram.memories.search(
    query="linen thread brand thickness",
    group=group,
    topics=["thread_preferences"],
    retrieval=HybridRetrieval(alpha=0.55, limit=5),
    scopes={"user_id": user_id},
)

Nora’s terse left-handed standing habits consolidate into one UserProfile memory that updates in place as she revises them. Glitter avoidance might land in the profile as a standing constraint, while “this season I like brand X linen” accumulates under thread_preferences. The agent loads the profile once per sitting and searches unbounded likes when thread choice is in play—simple where simplicity works, bounded where cardinality matters.

User profiles work best as Engram bounded topics scoped by user_id: one canonical memory, in-place updates, strict write gates, and unbounded topics beside them for accumulating likes. Our next chapter, How does preference learning work over time?, follows how those likes and standing tastes change across sessions without losing the profile’s single-slot promise.