Agent memory is often discussed as if the first design decision were “which vector database should we use?”
That is usually the wrong starting point.
Memory contains different kinds of data with different correctness and lifecycle requirements:
- structured user preferences
- task checkpoints
- past experiences
- semantic notes
- large artifacts
- temporary conversation state
- searchable historical episodes
Those things do not all belong in the same storage engine.
A better architecture starts by asking:
> What is the canonical form of this memory, how will it be updated or deleted, and which indexes are only derived views for retrieval?
In many production systems, PostgreSQL or another durable transactional store remains the source of truth, while vector search, Redis, and object storage are used selectively around it.
Start by separating canonical memory from retrieval indexes
Suppose the agent remembers:
User prefers Go examples.
You may store a canonical record such as:
{
"memory_id": "mem_123",
"scope": "user:42",
"type": "preference",
"key": "example_language",
"value": "Go",
"source": "explicit_user_statement",
"version": 3,
"created_at": "...",
"updated_at": "...",
"deleted_at": null
}
Then you might generate an embedding from the text and add it to a semantic index.
The embedding is not necessarily the canonical memory.
It is a derived retrieval representation.
That distinction makes updates and deletion far easier to reason about.
Canonical memory record
│
├── structured lookup
├── audit/history
└── embedding/search index
If the memory changes, update the source record and regenerate the derived index entry.
Match memory type to storage behavior
A useful first map looks like this:
| Memory/data type | Good default storage |
|---|---|
| User preferences / structured facts | PostgreSQL or document DB |
| Durable task/run checkpoints | Relational/transactional store |
| Semantic long-term recall | Postgres + pgvector or vector/search engine |
| Fast session/working state | Redis or durable low-latency KV depending requirements |
| Large reports/files/transcripts | Object storage |
| Append-only detailed run history | Event/log store or relational append-only table |
| Search index | Derived vector/lexical index |
The goal is not to use all of them.
It is to avoid making one database pretend to be all of them.
PostgreSQL is a strong default canonical memory store
For many SaaS/agent products, PostgreSQL already solves most of the difficult memory-management requirements:
- transactions
- tenant-scoped queries
- constraints
- versioning
- joins
- audit metadata
- soft deletion
- TTL/expiry workflows
- backups
- access control through application logic
A simple memory schema might look like:
CREATE TABLE agent_memories (
id uuid PRIMARY KEY,
tenant_id uuid NOT NULL,
user_id uuid,
agent_id uuid,
memory_type text NOT NULL,
memory_key text,
content text NOT NULL,
source_type text NOT NULL,
source_id text,
confidence numeric,
version bigint NOT NULL DEFAULT 1,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz,
deleted_at timestamptz
);
You can then create ordinary indexes for the most deterministic access paths:
CREATE INDEX agent_memories_user_idx
ON agent_memories (tenant_id, user_id, memory_type)
WHERE deleted_at IS NULL;
A query such as:
What is this user's preferred language?
may not need vector search at all.
A key lookup is faster, cheaper, and more deterministic.
Do not use semantic search when an exact lookup exists
This is one of the most common memory mistakes.
If memory has a stable key:
preferred_timezone
billing_currency
response_language
query the key.
Do not embed:
"The user's preferred timezone is Asia/Kolkata"
and hope nearest-neighbor search retrieves the right one every time.
Semantic retrieval is valuable for fuzzy questions such as:
Have we seen a similar migration problem before?
What did the user previously decide about deployment architecture?
Which past incident resembles this failure pattern?
Use exact storage for exact state and semantic search for semantic recall.
pgvector: semantic memory without another database
pgvector adds vector similarity search to PostgreSQL.
That can be a strong fit when:
- PostgreSQL already stores the canonical memory
- the vector corpus is manageable on your PostgreSQL infrastructure
- transactional metadata and semantic retrieval need to live close together
- you want one operational database instead of another stateful service
A table could include an embedding column:
ALTER TABLE agent_memories
ADD COLUMN embedding vector(1536);
Then semantic recall can combine vector ordering with normal relational filters:
SELECT id, content
FROM agent_memories
WHERE tenant_id = $1
AND user_id = $2
AND deleted_at IS NULL
ORDER BY embedding <=> $3
LIMIT 10;
The filtering is as important as the vector distance.
Memory retrieval should almost always be scoped before results reach the agent.
Exact vs approximate vector search
pgvector supports exact nearest-neighbor search by default.
It also supports approximate indexes such as:
- HNSW
- IVFFlat
These trade some recall characteristics for better search performance at scale.
HNSW
pgvector documents HNSW as generally offering a better speed/recall query trade-off than IVFFlat, at the cost of slower index construction and higher memory use.
IVFFlat
IVFFlat has lighter build characteristics but requires training/list configuration and careful probe tuning for recall.
The important point is not which acronym is “best.”
It is:
> Approximate search parameters must be tuned against your memory-retrieval evals, not copied from a blog benchmark.
Filtering can change vector-search recall
Memory retrieval almost always has filters:
tenant
user
agent
memory type
expiry
permission scope
With approximate vector indexes, filtering can interact with recall because candidate vectors may be scanned before the SQL filter removes rows.
pgvector provides several approaches such as:
- ordinary indexes on filter columns
- partial indexes
- table partitioning
- iterative index scans
This matters especially for multi-tenant memory.
A global ANN index containing every tenant's memories may have different performance/recall behavior from tenant-partitioned data.
Do not evaluate vector search only on an unfiltered benchmark.
Multi-tenancy belongs in the storage model
Every memory should have an explicit ownership scope.
At minimum:
tenant_id
user_id or principal
memory namespace/type
Never rely on the model prompt to say:
Only use memories belonging to this user.
The storage query should make cross-tenant retrieval impossible by construction.
Depending on scale and isolation requirements, you might use:
- shared tables with strict tenant predicates
- partitioning by tenant/group
- separate schemas/databases for higher isolation
- separate vector collections/namespaces
The architecture should be consistent with your broader SaaS tenancy model.
Redis: excellent for hot state, but define durability carefully
Redis can be useful for memory workloads because it provides very low-latency data access, TTLs, rich data structures, and vector search capabilities.
Redis vector search can operate over data stored in hashes or JSON and combine vector similarity with metadata filtering.
That makes Redis useful for workloads such as:
hot session state
working memory
short-lived conversation summaries
fast recent-memory recall
cached memory retrieval results
semantic search where Redis is already part of the platform
But “stored in Redis” should not automatically mean “this is our permanent source of truth.”
If the memory must survive incidents, rebuilds, or topology changes with strong durability expectations, explicitly decide whether Redis is:
canonical durable memory
or:
hot/cache/index layer derived from durable storage
For many systems, the second role is easier to operate safely.
TTL is especially useful for working memory
Not every memory should survive forever.
Redis-style TTL semantics map naturally to ephemeral state such as:
current research plan
recently accessed entity cache
short-lived conversation scratch data
temporary approval context
For durable relational memory, you can implement equivalent lifecycle behavior with an expires_at field and cleanup/archive jobs.
The important principle is that lifetime belongs in the data model.
Do not let old temporary state become accidental long-term memory because nobody built expiry.
Dedicated vector databases: use them when the workload justifies them
Dedicated vector/search platforms become attractive when semantic retrieval itself is a major workload.
Possible reasons include:
- very large vector collections
- high query concurrency
- specialized filtering/search requirements
- operational separation from the transactional database
- distributed vector indexing at scale
- managed vector-service preference
Examples include systems such as Qdrant, Weaviate, Pinecone, and Milvus.
But a dedicated vector database creates another production system to operate:
index freshness
backups/snapshots
replication
multi-tenancy
monitoring
embedding migrations
deletions
cost
If PostgreSQL + pgvector comfortably serves the workload, adding another database just because “AI memory needs a vector DB” is overengineering.
Vector index as derived state
A robust pattern is:
Canonical Memory Store
(PostgreSQL)
│
├── exact lookup
├── lifecycle + audit
└── change event / job
│
▼
Vector Search Index
The vector index can then be rebuilt from the canonical store if necessary.
This gives you a clean answer to questions such as:
What happens if the embedding index is corrupted?
How do we delete a user's memory?
How do we migrate to a new embedding model?
You still need synchronization, but ownership is clear.
Object storage for large memory artifacts
Not all memory belongs in rows.
Long-running agents may produce:
- reports
- datasets
- code snapshots
- PDFs
- large session ledgers
- generated artifacts
Store large objects in object/file storage and keep metadata + references in the canonical database.
For example:
{
"artifact_id": "art_123",
"tenant_id": "t1",
"kind": "incident_report",
"object_key": "tenant/t1/reports/art_123.md",
"summary": "...",
"embedding_status": "indexed"
}
Then index only the searchable text/summary/chunks needed for semantic recall.
Do not turn the vector store into blob storage.
Episodic history may deserve append-only storage
Agent experiences can be useful later:
what happened
what action was taken
what evidence was used
what outcome occurred
A detailed run ledger may be append-only while a separate distilled memory record captures the lesson.
Example:
raw run events
↓
immutable/auditable history
background consolidation
↓
compact episodic memory
↓
semantic retrieval index
This keeps forensic detail separate from the small memory representation shown to the model.
A practical hybrid architecture
A strong default for many agent products looks like this:
┌─────────────────────┐
│ Object Storage │
│ reports / artifacts │
└─────────▲───────────┘
│
Agent / App │
│ │
▼ │
Memory Policy Layer │
│ │
▼ │
┌────────────────────────────────────────────┐
│ PostgreSQL / Canonical Memory Store │
│ structured facts, scope, provenance, TTL, │
│ versions, deletion, task checkpoints │
└───────────────┬────────────────────────────┘
│
┌────────┴─────────┐
▼ ▼
Redis hot layer Semantic index
sessions/cache pgvector or dedicated DB
│ │
└────────┬─────────┘
▼
Context Retrieval
▼
LLM
You may not need every box.
Start with the canonical store and add layers only when measurements justify them.
Memory write path
A safe write flow can look like:
interaction produces memory candidate
↓
memory policy decides whether it should persist
↓
validate scope / sensitivity / provenance
↓
write canonical record transactionally
↓
queue index update
↓
generate embedding
↓
upsert derived semantic index
For high-value memories, canonical persistence should not depend on the vector index being available in the same synchronous request.
The index can catch up asynchronously if your product semantics allow it.
Memory read path
A retrieval flow may combine exact and semantic access:
request
↓
identify needed memory type
↓
┌───────────────┬──────────────────┐
exact key lookup│ semantic recall │
└───────────────┴──────────────────┘
↓
permission / expiry filtering
↓
confidence + recency ranking
↓
small memory set
↓
LLM context
The system does not need semantic search for every request.
Memory deletion is a distributed consistency problem
Suppose a user deletes a memory.
You may have copies in:
canonical database
Redis cache
vector index
search index
object storage
backups
At minimum, the online path should make deleted data unreachable immediately.
A deletion workflow can be:
mark/delete canonical memory
↓
revoke from normal reads
↓
remove cache entry
↓
remove vector/search entry
↓
remove or tombstone related artifacts where required
↓
record completion/audit state
If asynchronous deletion is used, track it explicitly rather than hoping every index eventually catches up.
Tombstones can help synchronize deletion
Instead of physically deleting the canonical record instantly, some systems use a tombstone:
deleted_at = timestamp
version = version + 1
Derived-index workers can consume that change and remove stale entries.
The application query filters deleted records immediately.
Whether you retain tombstones depends on privacy, compliance, and audit requirements, but the synchronization pattern is useful.
Embedding versions belong in the schema
Embedding models change.
Never store a vector without knowing which model/version produced it.
Useful metadata includes:
embedding_model
embedding_version
dimension
created_at
source_memory_version
Then an embedding migration can be deliberate:
v1 index serving production
↓
create v2 vectors in background
↓
compare retrieval evals
↓
switch reads
↓
retire v1
Do not overwrite the only working index before you know the new model performs better.
Memory updates need version control
Imagine this sequence:
09:00 user says preferred language = English
09:05 background worker embeds that memory
09:06 user changes preference = Hindi
09:07 old worker writes stale embedding after new value
Use version numbers or optimistic concurrency so derived updates can verify they are indexing the current canonical version.
Example:
memory version 4
embedding job expects version 4
if canonical version != 4:
discard stale job
This prevents out-of-order async workers from resurrecting stale memory.
Backups must cover the canonical layer
If the semantic index is rebuildable, its backup requirements may differ from the canonical store.
Ask:
If this database disappears, can we recreate it?
For canonical memory:
usually no → strong backup/PITR strategy.
For a vector index generated entirely from canonical records:
possibly yes → snapshots may speed recovery, but the source still exists.
This distinction can simplify disaster recovery.
Redis cache failure should not erase durable memory
If Redis is only the hot layer, the system should degrade to the durable store.
Design for:
Redis unavailable
→ memory lookups slower
→ durable store still authoritative
not:
Redis unavailable
→ agent forgot the user permanently
If Redis is canonical by design, then its durability and recovery configuration must match that responsibility.
Retrieval ranking should use more than vector similarity
Long-term memory relevance can depend on:
- semantic similarity
- explicit key match
- scope
- confidence
- recency
- memory type
- importance
- expiration
A simple ranking model might conceptually combine:
semantic relevance
+ recency weight
+ confidence
+ task-type compatibility
Do not assume the nearest vector is automatically the memory the model should see.
Do not inject too much memory
Storage can scale to millions of records. The model context should not.
Retrieve the smallest useful set.
A request about deployment architecture does not need:
favorite UI color
previous restaurant search
old unrelated project decisions
Memory quality is partly a storage problem and partly a context-selection problem.
Dedicated vector database vs pgvector: a practical decision
Start with PostgreSQL + pgvector when
- PostgreSQL already exists
- memory volume is modest/medium
- relational filters and transactions matter
- one datastore simplifies operations
- vector search is supporting the product, not the product itself
Consider a dedicated vector system when
- semantic search is a major high-scale workload
- vector corpus/concurrency is large
- specialized distributed indexing/features materially help
- independent scaling is valuable
- your team is willing to operate another stateful service
Do not migrate because of model parameter count or AI hype. Measure retrieval latency, recall, index growth, and operational load.
Production checklist
Before shipping a memory storage architecture, verify:
- Canonical memory ownership is explicit
- Exact facts use deterministic lookup where possible
- Vector search is used only where semantic recall helps
- Every memory has tenant/user/application scope
- Deletion propagates to caches and derived indexes
- Embedding model/version is stored
- Async index jobs cannot overwrite newer memory versions
- TTL/expiry exists for temporary memories
- Redis's durability role is explicit
- Large artifacts live outside ordinary vector rows
- Backups cover the true source of truth
- Semantic index can be rebuilt or has a recovery strategy
- Retrieval tests include real tenant filters
- Memory ranking considers more than vector distance
- Context selection limits how much memory reaches the model
Final takeaway
AI memory storage should look like good data architecture, not an “AI database” shopping list.
For many products, the simplest robust path is:
PostgreSQL → canonical memory and durable state
pgvector → semantic recall when scale fits
Redis → hot/session/ephemeral state and cache
Object storage → large artifacts
Dedicated vector DB → only when semantic-search scale/features justify it
The exact products can change. The responsibility boundaries should remain stable.
> Keep durable truth in a store you can update, audit, back up, and delete from reliably. Treat embeddings and vector indexes as retrieval structures unless you have a deliberate reason to make them the source of truth.
That principle makes memory easier to evolve as models, embedding systems, and retrieval technology change.

Discussion (0)