Short answer: A memory is a discrete durable fact—not a raw chat turn—with identity, content, topic, group, scope, and timestamps, embedded for hybrid retrieval.
A memory is Engram’s core unit of durable knowledge: curated state, not ephemeral messages or whole-transcript dumps. Pipelines create, update, and sometimes delete memories as they reconcile new input with what already exists. Engram extracts short, reusable statements so later turns load a few relevant facts instead of replaying everything. This chapter covers the fields a memory carries, how runs create-update-delete during processing, how search returns scored memories into prompts (hybrid by default; vector or BM25 when the use case is one-sided), and when to get or delete by id for compliance or corrections. Use delete for true erasure, not everyday edits; update or replace rather than layering contradictions forever. Log memory ids that entered a prompt when you need replayability.
A memory is Engram’s core unit of durable knowledge. It is not a raw chat turn and not a whole transcript dump. It is a discrete fact with identity, content, topic, group, scope, and timestamps, automatically embedded so Weaviate can retrieve it by meaning and keywords. Pipelines create, update, and sometimes delete memories as they reconcile new input with what already exists. This chapter explains what fields a memory carries, how memories move through create-update-delete during runs, how search returns scored memories into an agent prompt, and how get and delete give you precise control when compliance or corrections demand it.
What Makes a Memory Different From a Message?
Messages are ephemeral context. Memories are curated state. Engram extracts short, reusable statements from noisy input so later turns can load a few relevant facts instead of replaying everything. Each memory has an id, content, topic, group, and project_id. User-scoped memories also carry user_id. Property-scoped memories attach keys such as a session or job id in properties. created_at and updated_at track lifecycle. Search results add a score that ranking produced for that query.
That shape matters for agents. A prompt can inject five high-scoring memories about ink choice and paper sizing without dragging thirty turns of small talk. The unit stays small enough to rank well and large enough to state a complete fact. Engram embeds each memory as a vector in Weaviate so hybrid recall can find both plate codes and paraphrases.
If the memory is the unit of storage, the next question is how units appear and change after you call memories.add.
How Do Runs Create, Update, and Delete Memories?
Adding content starts a run. The pipeline extracts candidate facts, transforms them against existing memories, and commits operations. A completed run can list memories that were created, updated, or deleted. Creation inserts a new unit. Updates refine or merge when new information revises an old preference. Deletes remove superseded units when reconciliation decides the old statement should not remain. Bounded topics go further by keeping at most one memory per scope, so later writes update the same canonical object instead of spawning duplicates.
You usually do not craft those operations by hand. You send strings, conversations, or pre-extracted facts, then let the pipeline decide. Polling runs.wait is optional for fire-and-forget product paths. It is valuable in tests when you need the memory ids that were committed before you call get, search, or delete.
Retrieval is how those units earn their keep. Agents need ranked memories, not an unsorted table dump.
How Does Search Turn Memories Into Prompt Context?
Search asks Engram for the memories most relevant to a query inside a scope. Hybrid retrieval is the recommended default because operator language mixes exact tokens with fuzzy intent. You can also choose pure vector or BM25 when a use case is clearly semantic-only or keyword-only. Topic filters narrow which categories compete. Property filters narrow soft scopes. The response includes content plus scores so you can threshold weak hits before they enter the prompt.
A calligraphy atelier shows the full path from note to scored recall under a verified desk id.
import os
from engram import EngramClient, HybridRetrieval
client = EngramClient(api_key=os.environ["ENGRAM_API_KEY"])
desk = "ink-stone-3"
group = "calligraphy_atelier"
run = client.memories.add(
"Desk 3 ground pine-soot ink to a satin black for the kaishi practice set. "
"Keep the stone damp between passes. "
"Client prefers slightly cooler black than batch INK-77 from last month.",
user_id=desk,
group=group,
)
status = client.runs.wait(run.run_id)
created_ids = [op.memory_id for op in status.committed_operations.created]
hits = client.memories.search(
query="pine-soot ink temperature versus INK-77 for kaishi",
user_id=desk,
group=group,
retrieval_config=HybridRetrieval(limit=5),
)
assert any("INK-77" in m.content for m in hits)
assert all(hasattr(m, "score") for m in hits)
# Fetch one committed memory by id when you need the full record without ranking
if created_ids:
one = client.memories.get(created_ids[0], user_id=desk, group=group)
assert one.topic
assert one.content
The search hits are what you format into a system prompt. The get call is what you use when an audit trail or UI needs one exact unit. Both paths require the same scoping parameters the topic demands. Missing user_id on a user-scoped topic is not a soft filter. It is a rejected or empty path by design.
Sometimes the right operation is removal. Compliance and correction both need a clean delete story.
When Should You Get or Delete a Memory by ID?
Get retrieves one memory when you already know its id from a run, a console view, or a prior search. Delete removes that unit permanently. Deletion cannot be undone, so treat it as a deliberate act. Privacy workflows often search for a user’s memories, then delete each id under that same user_id and group. Afterward, hybrid search for the same query should return nothing for that user.
Do not confuse soft property filters with hard isolation. Omitting a conversation property searches across conversations for that user. Changing user_id moves you into another isolation boundary entirely. Engram enforces that boundary with Weaviate multi-tenancy underneath, so memory units stay owned by the scope that created them.
Healthy products treat memories as living records with clear ownership, not as anonymous blobs in a shared index.
What Habits Keep Memory Units Trustworthy Over Time?
Write short, factual content through the pipeline instead of stuffing paragraphs that try to be a whole biography. Prefer topic descriptions that pull clean facts. Inspect scores in staging and drop weak hits. When a preference changes, send new input and let transform update or replace the old unit rather than layering contradictions forever. Use delete for true erasure, not as the everyday way to edit.
Log memory ids that entered a prompt when you need replayability. Pair that with cross-user probes so a desk cannot read another desk’s ink notes. The memory unit is small on purpose. Engram’s value is keeping thousands of those units scoped, embedded, and searchable so agents can load the right few at the right time.
Our next chapter, What are groups in Engram?, zooms out from the single memory object to the group that bundles topics and pipelines. That is the configuration unit that decides which memories can exist in the first place.