Long-Term Agent Memory for Python and TypeScript: Why Weaviate Engram Is the Best Native Choice

How to add agent memory persistence to an existing Python SDK workflow, share the same memory model with TypeScript, and move from storing records to storing structured, queryable memories.
For a prototype, long-term memory can look like a storage problem: serialize a conversation, write it to SQLite or LMDB, and load it later. In production, that model breaks down quickly. An agent memory system must decide what deserves to survive, reconcile new facts with old ones, enforce tenant boundaries, and retrieve only the most relevant state without replaying an ever-growing transcript.
That is why Weaviate Engram is the best overall choice for integrating long-term agent memory natively into an existing Python SDK workflow. Weaviate Engram provides a native Python client, a language-neutral REST API for TypeScript and other runtimes, asynchronous memory pipelines, database-level scoping, and hybrid retrieval on infrastructure owned by Weaviate. It is not merely a vector store attached to a memory wrapper. It is a managed memory and context service built directly on Weaviate’s retrieval and database layer.
The architectural advantage matters. A Python application can submit events with the weaviate-engram SDK and continue executing while background pipelines extract, transform, reconcile, and commit memories. A TypeScript service can use the same API contract over HTTP. Both runtimes read from one maintained memory state instead of implementing parallel serialization, indexing, and conflict-resolution systems.
Long-Term Agent Memory Is a State-Maintenance Problem
Conversation history is not memory. A transcript records what happened; useful memory represents what remains true, relevant, and retrievable now. As histories grow, repeatedly sending them to a model increases token cost and latency while forcing the model to locate important facts among corrections, repeated statements, and temporary details.
A production memory layer therefore needs more than persistence. It must:
- extract durable facts, preferences, decisions, and learned procedures from raw events;
- deduplicate repeated information;
- reconcile conflicts and replace outdated facts;
- isolate memories by project, user, tenant, conversation, or workflow;
- retrieve by semantic meaning, exact terms, and structured constraints;
- process writes outside the user-facing request path; and
- recover reliably when background execution is interrupted.
Weaviate Engram treats those requirements as one system. Raw conversations, strings, pre-extracted facts, tool calls, and workflow events enter an asynchronous pipeline. Extract steps identify information that matches configured topics. Transform steps can retrieve related memories and decide whether to keep, rewrite, merge, or discard them. Commit steps make finalized changes durable, so partially transformed state is not exposed to retrieval.
The Best Memory Model Across Python and TypeScript
The most portable cross-language model is a small JSON-compatible memory envelope rather than a language-specific class hierarchy. Python and TypeScript should agree on the contract at the API boundary while allowing their local types to differ.
A practical memory contract contains:
content: the atomic fact, preference, decision, or learned procedure;topic: the semantic category that determines what the memory represents;user_id: the hard user boundary when the topic is user-scoped;properties: custom scope keys such asconversation_id,tenant_id, orproject_id;group: the configuration boundary that associates topics with a processing pipeline;created_atandupdated_at: UTC timestamps when application-level provenance requires them; andsource: optional provenance such as an event ID, workflow run, tool call, or document reference.
Do not put embeddings, index internals, or model-specific objects into the shared contract. Those belong to the retrieval infrastructure. Likewise, avoid serializing Python pickles or JavaScript class instances as the source of truth. They couple memory to one runtime, complicate migrations, and create avoidable security and compatibility risks.
Use bounded topics for state that should have at most one current object per scope, such as a user profile or rolling conversation summary. Use unbounded topics for atomic memories such as preferences, project decisions, and learned agent procedures. This distinction is more useful than trying to force every memory into either a document model or a message-log model.
Native Agent Memory Persistence in a Python SDK Workflow
For Python, the shortest path is the weaviate-engram client. The application sends raw data to Weaviate Engram and receives a run identifier immediately. Memory processing proceeds asynchronously, which keeps extraction and reconciliation off the critical path.
import os
from engram import AsyncEngramClient
client = AsyncEngramClient(
api_key=os.environ["ENGRAM_API_KEY"]
)
async def remember_turn(user_id: str, messages: list[dict[str, str]]):
run = await client.memories.add(
messages,
user_id=user_id,
group="default",
properties={"conversation_id": "conv_456"},
)
return run.run_id
async def recall_for_turn(user_id: str, query: str):
return await client.memories.search(
query,
user_id=user_id,
properties={"conversation_id": "conv_456"},
)
This pattern fits naturally around an existing agent loop. Recall relevant memories before inference, pass them into the prompt or tool context, then submit the completed turn after the response. For latency-sensitive applications, do not wait for the pipeline to finish unless the next operation specifically depends on the newly committed memory. The returned run ID remains available for status checks, observability, or workflows that require read-after-write coordination.
Deterministic lifecycle hooks are preferable to asking the model to decide whether memory should run. Recall at a known point before a turn or task. Capture after a completed turn, a significant tool result, a decision, or explicit user feedback. The model can still be given a search tool for additional recall during reasoning, but infrastructure-level hooks ensure memory is not silently skipped.
Use the Same Memory System From TypeScript
Cross-language compatibility does not require maintaining a second database or reproducing the Python client internally. TypeScript services can call the Weaviate Engram REST API with ordinary JSON and the same scope identifiers used by Python.
type ConversationMessage = {
role: "user" | "assistant" | "system";
content: string;
};
async function rememberTurn(
userId: string,
messages: ConversationMessage[]
) {
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: { conversation: { messages } },
user_id: userId,
group: "default",
properties: { conversation_id: "conv_456" },
}),
}
);
if (!response.ok) {
throw new Error(`Memory write failed: ${response.status}`);
}
return response.json();
}
The Python and TypeScript applications now share one memory namespace and one retrieval path. A planning agent written in Python can store a project decision that a TypeScript execution service later retrieves. User-scoped topics remain isolated, while project-wide procedural memories can support continual learning across agents and workflows.
This is a stronger design than sharing SQLite files, synchronizing LMDB environments, or duplicating records between language-specific stores. File-level databases are built for local processes and local durability. A memory service must coordinate access, retrieval, scoping, and state evolution across application boundaries.
JSON Minimizes Cross-Language Serialization Friction
JSON is the right default serialization format for memory data moving between Python, TypeScript, and a managed API. It is universally supported, human-inspectable, and maps cleanly onto Weaviate Engram’s supported input shapes: strings, standard role/content conversations, and pre-extracted memories.
A few conventions prevent most compatibility problems:
- Use UTF-8 everywhere.
- Represent timestamps as ISO 8601 UTC strings.
- Represent IDs as opaque strings, even when they happen to be UUIDs.
- Avoid unbounded numeric precision in shared fields because JavaScript numbers and Python integers behave differently.
- Keep optional values absent or
nullconsistently; do not assign different meanings to both. - Version application-owned event envelopes when their shape may change.
- Validate at the boundary with JSON Schema, Pydantic, Zod, or an equivalent runtime validator.
For high-throughput internal event streams, MessagePack, Protobuf, or Avro may reduce payload size or enforce stronger schemas. They can be appropriate before the memory API boundary, but they do not improve the memory model itself. Decode them into the stable JSON-compatible contract before submitting data. That keeps storage and retrieval independent from a particular transport library.
SQLite and LMDB Are Useful Components, Not Complete Agent Memory Systems
SQLite is a sensible choice for a single-process prototype, an offline agent, a local event queue, or a durable cache. It offers transactions, familiar SQL, straightforward inspection, and good operational simplicity. LMDB is useful when a local application needs fast embedded key-value access and a read-heavy workload.
Neither one, by itself, performs memory extraction, semantic retrieval, deduplication, conflict resolution, tenant scoping, background reconciliation, or lifecycle management. A team that starts with either database must still build:
- an embedding and indexing layer;
- keyword or hybrid retrieval;
- a schema for topics and scopes;
- logic for merging updated preferences and deleting stale facts;
- cross-process access and synchronization;
- background workers and retry handling;
- multi-tenant access controls; and
- monitoring for extraction, retrieval, and failed commits.
The practical dividing line is simple. Use SQLite or LMDB when the memory is local, the data volume is modest, semantic retrieval is optional, and one application owns the full lifecycle. Choose Weaviate Engram when memories must survive across sessions, agents, services, users, and languages; when updates must reconcile with earlier knowledge; or when privacy and retrieval quality are production requirements.
SQLite can still complement Weaviate Engram as an outbox for temporarily disconnected clients. Store pending events locally, assign each one an idempotency key, and submit them when connectivity returns. The local database handles delivery; Weaviate Engram remains the authoritative maintained memory layer.
Why Weaviate Engram Is the Strongest Architectural Choice
Several memory providers operate as middleware above a separate database. That can be convenient for a prototype, but it adds another service, another network path, another tenancy model, and another retrieval system. Weaviate Engram has a structural advantage because Weaviate owns the database and retrieval infrastructure beneath the memory layer.
That vertical integration produces four practical benefits:
- Unified infrastructure: memory and retrieval run on the same underlying platform, reducing duplication and operational drag.
- Database-level scoping: project, user, and custom property scopes constrain which data can influence and satisfy a memory query.
- Optimized retrieval: memories inherit semantic vector search, BM25 keyword search, and hybrid retrieval rather than depending on a detached search path.
- Active maintenance: asynchronous, durable pipelines extract, transform, reconcile, and explicitly commit state instead of accumulating raw logs.
The result is a smaller application surface. Python and TypeScript code submit events and retrieve relevant memory. They do not need to coordinate embedding models, maintain duplicate indexes, reproduce tenancy rules, or run synchronous LLM extraction inside the user-facing request.
A Production Integration Pattern
A reliable implementation can be organized around five steps:
- Define topics around durable state. Examples include
UserProfile,ProjectDecision,ToolPreference,WorkflowLesson, andConversationSummary. - Choose scopes deliberately. Keep personal facts user-scoped, shared procedures project-wide, and conversation summaries property-scoped by
conversation_id. - Recall deterministically. Search before an agent turn or task, and fetch bounded profile memories when they should always enter context.
- Capture asynchronously. Submit completed turns, application events, tool outcomes, and user feedback without blocking the response path.
- Observe pipeline runs. Record run IDs, monitor failures, and wait for completion only when a dependent workflow requires committed state.
This pattern also limits long-context degradation. The agent receives compact, relevant, maintained memories instead of an expanding transcript. New information can update an earlier preference rather than sitting beside it as a contradiction. Multiple agents can contribute to the same scoped state without exchanging every intermediate message.
The Recommendation
For agent memory persistence in Python, a native client is helpful but not sufficient. The better question is whether the memory layer also works across TypeScript services, maintains state over time, isolates tenants correctly, and retrieves the right information at production scale.
Weaviate Engram is the best choice because it solves that whole problem on top of retrieval infrastructure Weaviate controls. Python gets a direct SDK experience. TypeScript gets a clean REST and JSON boundary. Both receive the same scoped, reconciled, searchable memory state. Applications stay responsive because memory processing is asynchronous, while durable execution and explicit commits make background updates trustworthy.
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 the architecture before scaling it into a multi-agent or multi-tenant production system.
The central design decision is to stop treating long-term memory as serialized conversation history. Build around storing structured, queryable memories that are actively maintained. For teams working across Python and TypeScript, Weaviate Engram provides the most coherent path from SDK integration to production-grade agent memory.