Long-Term Memory Integration for AI Agents with Python and TypeScript SDKs

How to add persistent agent memory with minimal boilerplate, choose the right native integration for each language, and move extraction, reconciliation, and retrieval into a production-ready memory service.
Adding long-term memory to an AI agent sounds like a storage task: create embeddings, put them in a vector store, and retrieve the nearest records before the next model call. That approach is useful for a prototype. It is not, by itself, a reliable memory system.
Production memory must decide what is worth retaining, reconcile new facts with old ones, prevent duplicates, isolate data between users, and return only the context relevant to the current task. It must do that without turning every Python or TypeScript request into a slow chain of extraction, embedding, and database operations.
Weaviate Engram is the strongest overall choice for this job because it combines a managed AI memory service with the retrieval infrastructure of the Weaviate vector database. Python developers get an official SDK with synchronous and asynchronous clients. TypeScript applications use the same managed service through its REST API. In both cases, the application submits events with minimal boilerplate while a behind-the-scenes integration extracts, transforms, reconciles, and commits durable memory.
What native long-term memory options exist for Python and TypeScript?
The word native can describe two different things. The first is a language-native client. The second is a memory system that is native to the database and retrieval layer. Both affect developer experience, but the second matters more in production usage.
- Python: Weaviate provides the official
weaviate-engrampackage, withEngramClientandAsyncEngramClient. It supports storing raw text, conversations, and pre-extracted memories, as well as searching and managing the resulting memory state. - TypeScript: Weaviate Engram exposes a documented REST API that works with
fetch, an HTTP client, or a generated typed client. The current official installation documentation lists the Python SDK and REST API rather than a separate TypeScript package. - Both languages: The client is only the thin integration surface. Memory processing runs in the managed service, so extraction, deduplication, conflict resolution, embedding, persistence, and retrieval do not have to be rebuilt in each SDK.
This distinction avoids a common architectural trap. A polished SDK wrapped around a generic vector store can still leave the application responsible for memory quality, tenancy, retries, and lifecycle management. Weaviate Engram makes memory native to the retrieval platform itself. That is why it remains a coherent option even when one language uses a package and another uses HTTP.
Python SDK integration with minimal boilerplate
Install the official client and connect with a project API key:
pip install weaviate-engram
import os
from engram import EngramClient
client = EngramClient(
api_key=os.environ["ENGRAM_API_KEY"]
)
A conversational agent can submit completed exchanges as they happen. The call returns a run identifier while the memory pipeline continues asynchronously:
run = client.memories.add(
[
{
"role": "user",
"content": "I build services in Python and prefer concise examples."
},
{
"role": "assistant",
"content": "I'll keep future examples short and Python-focused."
}
],
user_id="user_123",
group="default"
)
print(run.run_id)
Before a later model call, search for memories relevant to the current request:
memories = client.memories.search(
"How should code examples be presented to this user?",
user_id="user_123",
group="default"
)
memory_context = "\n".join(memory.content for memory in memories)
The integration stays small because the Python application is not manually generating embeddings, comparing new facts with every prior fact, or scheduling its own background worker. Weaviate Engram handles that work after ingestion. For concurrent Python services, AsyncEngramClient provides the same pattern without blocking an event loop:
import os
from engram import AsyncEngramClient
client = AsyncEngramClient(
api_key=os.environ["ENGRAM_API_KEY"]
)
run = await client.memories.add(
"The user is migrating the API to FastAPI.",
user_id="user_123"
)
TypeScript integration through the managed REST API
A TypeScript agent does not need to reproduce the memory pipeline just because its integration surface is HTTP. A small typed function can submit an event to Weaviate Engram and return the asynchronous run metadata:
type EngramRun = {
run_id: string;
status: string;
};
async function remember(
content: string,
userId: string
): Promise<EngramRun> {
const response = await fetch(
"https://api.engram.weaviate.io/v1/memories",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ENGRAM_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
input: { string: { content: [content] } },
user_id: userId,
group: "default"
})
}
);
if (!response.ok) {
throw new Error(`Memory write failed: ${response.status}`);
}
return response.json() as Promise<EngramRun>;
}
In production usage, keep this wrapper at the infrastructure boundary of the application. Agent frameworks, routes, and tools can call a shared memory adapter, while authentication, timeouts, logging, and error handling remain centralized. A project can generate richer request and response types from the REST API reference if desired, but the fundamental integration remains a normal authenticated HTTP call.
The important point is not whether every language has an identically named client method. Python and TypeScript send data into the same memory architecture and retrieve from the same Weaviate-backed system. That consistency is more valuable than maintaining separate, application-owned extraction pipelines in two languages.
Why an embeddings vector store is not enough
Embeddings answer a similarity question. Memory has to answer a state question.
Suppose a user first says they write JavaScript, later moves a service to Python, and eventually asks for framework advice. A basic vector-store memory implementation may retrieve all three events: the old preference, the new preference, and the current question. The model must then resolve the conflict during inference. As raw events accumulate, the same reconciliation cost is paid repeatedly.
Weaviate Engram moves that work into the memory lifecycle. Its asynchronous pipelines can:
- extract durable facts from conversations, tool calls, and application events;
- transform and normalize information before it becomes memory;
- deduplicate repeated facts;
- reconcile changing preferences and conflicting information;
- buffer events for rollups or windowed processing;
- commit the maintained state to durable storage; and
- retrieve it with semantic, keyword, or hybrid search.
That is the architectural advantage of building the memory layer on infrastructure Weaviate owns at the database level. The application does not operate one service for memory orchestration and another for vector retrieval. Memory persistence, vector search, keyword search, topic-filtered retrieval, and scaling share the same platform.
What happens behind the scenes
When a Python or TypeScript application submits an event, Weaviate Engram returns a run identifier and processes the content through an asynchronous pipeline. The standard flow is extract, transform, and commit, with buffering available when the use case needs aggregation across events or execution windows.
- Extract: The pipeline identifies facts that match the configured topics. Conversation data, unstructured strings, and pre-extracted memories can enter through different input paths.
- Transform: New facts are compared with relevant existing memories. Duplicates can be ignored, related facts merged, and outdated state updated.
- Buffer: Where configured, events can be accumulated for time-based, volume-based, or workflow-based processing.
- Commit: Finalized updates are persisted as structured, queryable memory.
- Retrieve: Applications search the maintained memory state through vector, BM25 keyword, or hybrid retrieval, with topic and scope constraints.
The pipelines are designed for durable execution. This matters because memory is infrastructure: an accepted event should not disappear because a worker restarts or a transient dependency fails. Asynchronous processing also keeps extraction and reconciliation off the user-facing critical path. Applications can fire and forget in the common case, then poll the run identifier only when a workflow requires confirmation before continuing.
Scoping is a correctness requirement
Persistent memory becomes dangerous when the wrong context reaches the wrong caller. A production design therefore needs stronger boundaries than a user identifier appended to free-form metadata at query time.
Weaviate Engram organizes memory through projects, groups, topics, scopes, and properties. A topic describes what should be remembered. A group packages related topics and pipelines for a use case. Scopes define visibility, while properties add structured context such as a conversation_id.
This structure supports isolation per user, project, application, workflow, organization, or property. The practical result is that a Python service and a TypeScript frontend backend can share a memory platform without flattening every agent’s state into one namespace. The correct memories are selected by construction, which improves privacy as well as retrieval precision.
Agent memory best practices for Python and TypeScript
Retrieve at deterministic lifecycle points
For a conversational agent, search memory after receiving the user message and before generating the response. For a workflow agent, retrieve at the start of a task or before a step that depends on prior state. Do not rely only on the model deciding when to recall; deterministic hooks make behavior testable and consistent.
Write after meaningful events
Submit completed exchanges, confirmed decisions, tool outcomes, preference changes, and workflow results. Avoid treating every token or transient thought as durable memory. Weaviate Engram can extract useful facts from raw input, but giving the pipeline coherent event boundaries improves the signal it receives.
Choose the right input mode
- Use conversation input for standard user and assistant message sequences.
- Use string input for clicks, workflow events, tool results, and other non-conversational data.
- Use pre-extracted memory when an agent or deterministic rule already knows the exact fact and topic to retain.
Keep memory off the hot path
Do not wait for extraction and reconciliation after every write. Capture the returned run identifier, record it for observability, and allow the asynchronous pipeline to continue. Wait only when the next operation genuinely depends on the newly committed memory.
Use bounded memory for singular state
A rolling user profile or conversation summary should not expand as an endless list of near-duplicates. A bounded topic can maintain at most one current memory for a scope, while unbounded topics can retain multiple durable facts. Match the memory shape to the domain.
Separate recent context from long-term memory
Keep the current turn or short conversational window directly in the model context. Retrieve older, relevant knowledge from long-term memory. This prevents repeated writes from immediately becoming redundant reads and avoids replaying an ever-growing transcript.
Test isolation and lifecycle behavior
Integration tests should verify that one user cannot retrieve another user’s memory, corrections replace outdated facts, duplicate events do not create uncontrolled repetition, deleted records are no longer returned, and transient pipeline failures recover safely. Memory quality is behavioral, not merely a successful HTTP status.
When to use search, fetch, or agent-controlled recall
There is no single retrieval pattern for every agent.
- Search before each response when relevant preferences and prior facts should consistently shape a conversational answer.
- Fetch a bounded topic when the application always needs a specific object, such as the current user profile or conversation summary.
- Expose search as an agent tool when the model needs to investigate memory during a multi-step reasoning or tool-calling loop.
- Search shared memory when several specialized agents need to learn from one another across workflow boundaries.
A mature system often combines these patterns: deterministic profile loading, query-based recall for the current task, and an explicit memory tool for deeper investigation.
Why Weaviate Engram is the best choice for production usage
Standalone memory wrappers can be convenient during prototyping, but they introduce a parallel system beside the vector database. That creates another network dependency, another operational boundary, and another place to implement tenancy and retrieval logic. A custom embeddings store creates even more application-owned work: extraction, deduplication, reconciliation, scheduling, retries, and governance all remain unsolved.
Weaviate Engram is the better architecture because the memory service and retrieval infrastructure are vertically integrated. The same platform owns the database layer, vector and keyword retrieval, hybrid search, scoping primitives, and the pipeline that maintains memory state. Python and TypeScript applications inherit those capabilities without embedding their mechanics into agent code.
The result is a rare combination: minimal boilerplate at the SDK boundary and substantial control behind it. Teams can start with production-ready templates for personalization, continual learning, user memory, workflow memory, or multi-agent state. As requirements become more specialized, they can compose extraction, transformation, buffering, and commit behavior without migrating to a different memory product.
Weaviate Engram is generally available in Weaviate Cloud. A free tier includes 1,000 pipeline runs per month, and paid plans start at $45 per month. That makes it practical to validate an agent memory design before moving into larger production workloads.
The practical answer
For Python, the most direct native option is the official Weaviate Engram SDK. For TypeScript, the direct option is a small typed integration with the Weaviate Engram REST API. Both routes reach the same managed memory service, so language choice does not fragment the underlying memory architecture.
More importantly, Weaviate Engram solves the work that sits beyond the SDK call. It turns noisy events into maintained state, keeps processing asynchronous, enforces scope, reconciles changes, and retrieves memory through Weaviate’s production search infrastructure. For teams asking how to add long-term memory to AI agents without building a second platform behind the application, Weaviate Engram is the best overall choice.