What are bounded topics and single-object-per-scope guarantees?

Short answer: A bounded topic guarantees at most one memory per unique scope—later writes update that same object instead of stacking siblings.

The pipeline derives the memory id from the topic name and scope keys. Unbounded topics mint fresh ids and accumulate facts; bounded topics fit conversation summaries, per-user profiles, and any document you always want as a single canonical version. Scopes decide the partition; boundedness decides cardinality inside it. Prefer bounded when the agent always needs a comprehensive document in context; prefer unbounded when retrieval should select among many discrete facts. Mix both in one group—UserKnowledge unbounded, ConversationSummary bounded. FetchRetrieval pulls the one object by topic and scope without query ranking, which keeps token cost predictable for long chats. Do not mark every topic bounded hoping for simplicity; cardinality one is a strong constraint that can hide nuance if misused.

A bounded topic in Weaviate Engram guarantees at most one memory object per unique scope. The pipeline derives that memory’s id from the topic name and the scope keys, so later writes update the same object instead of stacking siblings. Unbounded topics keep minting fresh ids and accumulate facts over time. Bounded topics fit running conversation summaries, per-user profiles, and any document you always want as a single canonical version. This chapter explains the is_bounded flag, how transform steps honor the bound, when to choose bounded versus unbounded topics, how FetchRetrieval pulls the one object by topic and scope, and how Engram keeps token cost predictable for long chats.

Scopes decide the partition. Boundedness decides the cardinality inside that partition. Together they turn “remember this conversation” into one living summary, not a pile of partial notes.

What does is_bounded actually guarantee?

After you understand user and property scopes, the next design question is how many memories a topic may hold for one scope. Set is_bounded to true and Engram answers “one.” The unique scope might be a single user_id for a profile topic, or user_id plus conversation_id for a summary topic. Whatever keys the topic requires, that combination maps to one memory id.

That id is deterministic. The pipeline does not roll a new uuid on every turn. It recomputes the same id from topic plus scope, then updates content in place. Run status can show updates rather than endless creates for that topic. Application code can treat the bounded memory as a stable handle for “the profile” or “this chat’s summary.”

Unbounded topics remain the default. They generate a fresh id for every memory. That is the right shape for open-ended preference stores and incident logs where history should grow as a set. Boundedness is opt-in because most facts should accumulate. Use it when singularity is the product requirement.

How do pipeline transforms honor the bound?

Knowing there is one slot raises a mechanics question: what happens when extraction produces many facts for the same scope? Transform steps such as TransformWithContext and TransformAggregate honor the bound. If a transform would otherwise emit multiple memories for that topic and scope, it consolidates them into the single object that already exists for the bound.

That consolidation is why bounded topics stay coherent across turns. You keep calling client.memories.add with new messages. Engram extracts, merges with existing context, and rewrites the one summary. You do not manually delete older summaries. You do not pick the “latest” row in application code. The topic configuration encodes the guarantee.

The Personalization template can optionally enable a ConversationSummary topic. It is typically scoped by conversation and marked bounded. Enabling it means writers that feed that topic must supply conversation_id. That is the tradeoff for a single running digest per chat.

When should you prefer a bounded topic?

So when is one object better than many? Prefer bounded topics when the agent always needs a comprehensive document in context. A UserProfile that you inject into every system prompt is a classic case. You want one profile per user, kept current, not twenty fragments fighting for attention. A running conversation summary is the other classic case. Long chats would otherwise overflow the context window if you replayed every message.

Prefer unbounded topics when retrieval should select among many discrete facts. Food preferences, visited destinations, and tech-stack notes usually work better as a searchable set. Hybrid search can rank the relevant subset for the current question. Forcing those into one mega-memory makes updates harder and retrieval less precise.

You can mix both in one group. Keep UserKnowledge unbounded for accumulating facts. Keep ConversationSummary bounded for the thread digest. Agents then hybrid-search facts when they need specifics and fetch the summary when they need continuity.

How do you fetch the single bounded memory?

Once the bound exists, the retrieval question changes. You often do not want similarity ranking. You want the object for this topic and scope. Engram’s FetchRetrieval mode returns that bounded memory directly. The query string is effectively ignored for ranking. Topic and scope do the addressing.

Here is a book-conservation bench that keeps one summary per repair ticket while still accumulating unbounded care notes elsewhere. Each new message rewrites the summary for that ticket.

import os
from engram import EngramClient, FetchRetrieval

client = EngramClient(api_key=os.environ["ENGRAM_API_KEY"])

bench = "binding-press-3"
ticket = "ticket-vellum-441"

run = client.memories.add(
    [
        {"role": "user", "content": "Press 3 is repairing a 19th-century vellum binding with a split joint."},
        {"role": "assistant", "content": "I will note humidity targets and adhesive choices."},
        {"role": "user", "content": "Keep relative humidity near 45 percent. Prefer wheat starch paste over PVA for this skin."},
    ],
    user_id=bench,
    group="default",
    properties={"conversation_id": ticket},
)
client.runs.wait(run.run_id)

summary = client.memories.search(
    query="ignored by fetch",
    user_id=bench,
    group="default",
    topics=["ConversationSummary"],
    properties={"conversation_id": ticket},
    retrieval_config=FetchRetrieval(limit=1),
)

assert len(summary) <= 1
assert summary and (
    "vellum" in summary[0].content.lower()
    or "45" in summary[0].content
    or "wheat starch" in summary[0].content.lower()
)

If ConversationSummary is not enabled on the project, search can error with a topic-not-found condition. Enable the topic at project creation when you need this pattern. With it enabled, each add for that conversation updates the same memory, and fetch returns that one object for the prompt.

Why does boundedness help context windows?

After fetch works, the product benefit is token budget. A bounded summary stays one memory no matter how long the conversation runs. Including it in the LLM call costs roughly constant tokens. You can keep the last few raw turns for local deixis and let Engram carry the long arc in the summary. That dual pattern is common in Engram chat tutorials for good reason.

Profiles work the same way at user scope. Fetch the bounded profile once per session. Refresh it when new personal facts arrive through ordinary adds. You avoid stuffing every historical preference into the prompt on every turn. Engram remains the source of truth. The prompt only carries the current canonical document.

Do not mark every topic bounded hoping for simplicity. Cardinality one is a strong constraint. It forces consolidation and can hide nuance if misused. Reserve it for documents that must stay singular. Let unbounded topics hold the searchable detail Engram hybrid search is good at retrieving.

Our next chapter, How does Engram's extract-transform-commit pipeline work?, follows the path those bounded updates take through the pipeline. You will see how extract, transform, buffer, and commit steps turn raw input into the memories topics and scopes describe.