Short answer: Each memories.add accepts exactly one shape: a string, a conversation, or pre-extracted items—all three enter the same durable pipeline.
String and conversation inputs use LLM extraction guided by your topics; pre-extracted items skip extraction and still flow through transform and commit. Choosing the type is about who decides what counts as a memory—Engram or your agent. Conversations fit chat turns with roles; plain strings fit product events and terse notes; pre-extracted fits tool calls like “remember this” where you control wording and topic routing. Engram still deduplicates, merges, and persists. Scoping rules apply the same way across all three. Do not convert every analytics event into synthetic chat, and do not dump unscoped essays under a random topic via pre-extracted. Match the shape to the source of truth in your app so all roads still end in one memory store with hybrid search.
Weaviate Engram accepts exactly one input shape per memories.add call: a string, a conversation, or pre-extracted items. Each shape is a different entrypoint into the same durable pipeline. String and conversation inputs use LLM extraction guided by your topics. Pre-extracted items skip that step and still flow through transform and commit. This chapter explains when to use each type, how message roles work for chat, why product events prefer strings, how agent tool calls use pre-extracted facts, and how scoping rules apply the same way across all three.
Choosing the input type is not a branding decision. It is a question about who should decide what counts as a memory. Engram can decide for you, or your agent can decide and hand Engram structured items.
Why does Engram offer three entrypoints instead of one?
After pipelines and durability, the natural question is what you put on the wire. Real applications produce different raw shapes. Chat turns already look like role and content pairs. Product telemetry looks like free-form event lines. Tool-calling agents sometimes invent the memory text themselves. Forcing every path into one JSON shape either loses structure or invents fake dialogue.
Engram maps those realities to three content types. string covers notes and observations. conversation covers multi-turn transcripts. pre_extracted covers items that already name their topic. Exactly one type is used per call. Mixing them in a single request is not supported. Send separate adds when you have both a chat turn and an unrelated event.
All three still share group, user, and property scoping. Required scopes come from topic configuration, not from the content type. A user-scoped topic needs user_id whether you send a string or a conversation.
When should you send conversation messages?
Knowing the three doors exist, chat is usually the first one you open. Pass a list of message dicts with role and content. Roles follow the common chat completions set: user, assistant, system, tool, and developer. Tool calls are supported. The server normalizes tool to user and developer to system internally.
You do not need a finished conversation. Send new messages as they happen. Engram’s conversation extract step is built for dialogue, so it can pull facts like preferences and plans from the exchange. In a typical assistant, call add after each turn or short burst of turns. Keep the latest messages in the prompt. Let Engram own the durable residue for later sessions.
Conversation input is the wrong fit for “user opened settings” or “invoice paid.” Those are not speaker turns. Stuffing them into fake assistant messages makes extraction noisier. Use a string for those events instead.
When is a plain string the better input?
Strings are the flexible path for data that is not chat. Agent observations, page views, workflow milestones, and operator notes all fit. The pipeline still extracts topic-matching memories. You simply avoid pretending the event was spoken in a dialogue.
One call can include multiple strings in the content array. Each becomes its own pipeline input. That helps when you batch unrelated observations. Prefer separate calls when order and scope sequencing matter for reconciliation. Prefer a multi-string call when the notes are independent snapshots for the same scope.
String extraction still depends on topic descriptions. A vague topic will under-extract. A sharp topic will pull the facts you care about from terse event text. Tune descriptions before inventing a custom extract agent.
When should you use pre-extracted items?
Sometimes your agent should decide what to remember. Tool calls like “remember this” are the classic case. Pre-extracted input is the escape hatch. Each item carries content and a target topic. Engram skips LLM extraction and passes the items into transform and commit. You keep control of wording and topic routing. Engram still deduplicates, merges, and persists.
That split matters. Extraction is where you might want agent judgment. Reconciliation and storage are still Engram’s job. Do not bypass the whole pipeline by writing directly to an ad hoc store. Use PreExtractedInput and PreExtractedItem so transforms and commits stay consistent with conversation-derived memories.
Here is a lacemaking studio assistant that records a short chat, an equipment event as a string, and a tool-chosen fact as pre-extracted content for the same pillow bench.
import os
from engram import (
EngramClient,
HybridRetrieval,
PreExtractedInput,
PreExtractedItem,
)
client = EngramClient(api_key=os.environ["ENGRAM_API_KEY"])
pillow = "lace-pillow-3"
chat_run = client.memories.add(
[
{"role": "user", "content": "Pillow 3 is working a Honiton spray with finer gimp than last month."},
{"role": "assistant", "content": "I will remember the gimp preference for this bench."},
{"role": "user", "content": "Keep bobbins wound clockwise only for this pattern."},
],
user_id=pillow,
group="default",
)
event_run = client.memories.add(
"Sensor: pillow 3 lamp dimmed to 2700 K during the evening shift.",
user_id=pillow,
group="default",
)
tool_run = client.memories.add(
PreExtractedInput(items=[
PreExtractedItem(
content="Bench prefers Midland cloth for Honiton grounds on bridal commissions.",
topic="UserKnowledge",
),
]),
user_id=pillow,
group="default",
)
client.runs.wait(tool_run.run_id)
hits = client.memories.search(
query="Honiton gimp bobbin direction cloth and evening lamp",
user_id=pillow,
group="default",
retrieval_config=HybridRetrieval(limit=8),
)
assert any("Honiton" in m.content or "gimp" in m.content.lower() for m in hits)
assert any("2700" in m.content or "Midland" in m.content for m in hits)
Three shapes, one scope, one searchable memory set. Conversation captured craft preferences from dialogue. The string captured a non-chat sensor event. Pre-extracted captured an explicit remember action with a chosen topic.
How should you choose among the three in product code?
Default to conversation for human chat and agent transcripts. Default to string for application events and free-form notes. Reach for pre-extracted when a tool or upstream system already named the topic and content. If you are unsure, ask whether an LLM should discover the fact. If yes, use string or conversation. If no, use pre-extracted.
Keep adds small and frequent. Durable execution will order them per scope. Do not wait for a whole session to finish before sending conversation fragments. Do not convert every analytics event into a synthetic chat. Do not use pre-extracted to dump unscoped essays under a random topic name. Match the shape to the source of truth in your app.
Engram’s value is that all three roads still end in the same memory store, with the same transforms and the same hybrid search. Your integration stays simple. The pipeline stays consistent. The agent gets one place to remember.
Our next chapter, What retrieval types does Engram support?, turns from writing inputs to reading them back. You will see how vector, BM25, hybrid, and fetch retrieval each answer a different recall question.