Production-ready guide covering ai agent memory architecture design patterns with implementation patterns, code examples, and anti-patterns for enterprise engineering teams.
AI agents operate across time and context, requiring persistent, structured, and accessible memory to make coherent decisions, track state, and learn from experience. Memory architecture design patterns define how agents store, retrieve, update, and reason over knowledge—both internal and external—across sessions, tasks, and interactions. This page details the patterns that solve recurring challenges in building scalable, reliable, and performant memory systems for AI agents.
An agent begins a task by querying its memory for relevant context. The pattern begins with a query vector, computed from a current input (e.g., user message, sensor reading, code snippet), then performs a similarity search over a vector database.
-- Vector store schema: embeddings are stored as `vector` (double precision[]), indexed with GiST
CREATE TABLE agent_memory (
id UUID PRIMARY KEY,
context TEXT NOT NULL,
embedding vector(1536) NOT NULL,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX ON agent_memory USING ivfflat (embedding vector_ops)
WITH (lists = 100, dim = 1536);
The agent executes a nearest-neighbor query using a standard similarity metric (e.g., cosine distance). The top-k results are retrieved and merged into the agent’s context.
POST /v1/agents/memory/search
Content-Type: application/json
{
"query": "What are the key decisions from the Q3 roadmap?",
"k": 5,
"similarity": "cosine",
"filter": {
"source": "meeting_notes",
"year": 2024
}
}
Failure mode: When the query embedding is not normalized, cosine similarity degrades. The agent retrieves semantically similar but numerically mismatched vectors. To fix, normalize embeddings during insertion and during query.
Sharp edge: The filter object must match the metadata JSONB structure exactly. A typo in a key—"yeat" instead of "year"—causes the filter to return no results, silently.
Agents record high-fidelity, immutable records of decisions, observations, and actions. This pattern uses an append-only log to store memory events, with each entry versioned and timestamped.
{
"event_id": "evt_1a2b3c",
"timestamp": "2024-09-15T14:32:18.000Z",
"agent_id": "agent_001",
"type": "decision",
"payload": {
"title": "Chose PostgreSQL over MySQL for analytics layer",
"reasons": [
"Better JSONB support",
"Better indexing for time-series data"
],
"proposed_at": "2024-09-14T10:00:00Z",
"decision_by": "[email protected]"
},
"context": {
"current_task": "database_migration",
"user_input": "We need a DB that handles nested data well."
}
}
The log is stored in a distributed, durable queue—e.g., Apache Kafka or AWS Kinesis—where each event is a message.
kafka-console-consumer.sh \
--bootstrap-server kafka.example.com:9092 \
--topic agent_memory_log \
--partition 0 \
--offset latest \
--max-messages 100
Failure mode: Agents write to the log but fail to commit offsets. After a crash, they reprocess events, causing duplicate memory entries. To avoid this, use idempotent producers with acks=all and enable.idempotence=true.
Sharp edge: The event_id must be globally unique. Using a UUID ensures no collisions, but a simple counter-based ID can lead to lost events during broker failure. Use event_id as the primary key in downstream storage.
Agents frequently update existing memory entries. Rather than replacing the entire record, they apply deltas—small changes—over time.
Each update is a patch operation sent as a PUT or PATCH request to a memory API.
PATCH /v1/agents/memory/{memory_id}
Content-Type: application/json
{
"changes": {
"metadata": {
"status": "reviewed",
"reviewer": "[email protected]",
"updated_at": "2024-09-16T11:20:00Z"
},
"context": {
"notes": "Added comparison to ClickHouse.",
"references": [
"https://example.com/clickhouse-vs-postgres"
]
}
},
"merge_strategy": "deep"
}
The server applies the changes using a deep merge algorithm: nested objects are merged recursively, arrays are appended, and primitives are replaced.
Failure mode: A merge_strategy of "shallow" flattens the structure, overwriting nested objects instead of merging. A context field updated via shallow merge loses prior notes.
Sharp edge: The merge_strategy key is case-sensitive. deep, Deep, and DEEP are not equivalent. The agent must consistently use one.
For agents operating across long-running sessions, memory snapshots capture a full state at a point in time.
A snapshot is a complete export of memory data, including:
Snapshots are stored as tarballs or compressed JSON files.
# Create snapshot
tar -czf \
agent-snapshot-2024-09-15-1432.tar.gz \
--transform 's,^,snapshot/,g' \
memory_vectors.json \
memory_events.json \
agent_state.json \
context_cache/
Each snapshot is versioned and stored under a consistent path:
/snapshots/agent_001/2024-09-15T14:32:18.000Z/
The agent can restore its memory by loading the snapshot into memory and rebuilding its vector index.
Failure mode: The snapshot contains stale embeddings. After a new entry is added, the agent must recompute the vector index. If it skips this step, the agent retrieves outdated context.
Sharp edge: The transform option in tar must preserve the directory structure. A missing --transform causes the snapshot to fail to load due to incorrect file paths.
Agents use multiple memory layers, each with different scopes and access patterns.
The agent composes context by querying all layers in sequence.
def get_context(query: str, session_id: str) -> dict:
# 1. Local memory: session-specific
local_context = redis.hgetall(f"session:{session_id}:memory")
# 2. Global memory: vector search
global_results = vector_db.search(
query=query,
k=10,
filter={"scope": "global"}
)
# 3. Event memory: recent events
recent_events = postgres.query("""
SELECT * FROM agent_events
WHERE agent_id = $1
AND created_at > NOW() - INTERVAL '1 hour'
ORDER BY created_at DESC
LIMIT 5
""", session_id)
return {
"local": local_context,
"global": global_results,
"events": recent_events
}
Failure mode: The agent forgets to update the local memory after a global query. After retrieving global context, it must merge the results into the local state.
Sharp edge: The scope field in the vector database must be exact. "global" is not equivalent to "Global" or "global_memory". The agent must normalize keys during indexing.
When multiple agents write to the same memory item, conflicts arise. The pattern resolves these using a timestamp-based or version-based strategy.
Each memory entry includes a version field and last_updated timestamp.
{
"id": "mem_123",
"title": "Database Schema Design",
"content": "Schema for user and order tables...",
"version": 5,
"last_updated": "2024-09-15T12:00:00Z",
"writers": [
"agent_001",
"agent_002"
]
}
When agent agent_002 updates the entry, it sends:
PUT /v1/agents/memory/mem_123
If-Match: "5"
Content-Type: application/json
{
"content": "Updated schema with product category and pricing.",
"version": 6,
"last_updated": "2024-09-15T12:05:00Z"
}
The server checks the If-Match header. If the current version is 5, it applies the update and increments the version. If version 6 is already present, it returns 412 Precondition Failed.
Failure mode: Two agents update the same memory item in parallel. One applies its change, but the other’s update overwrites it. The agent must use If-Match to detect and resolve conflicts.
Sharp edge: The If-Match header value must be the exact string of the version field. If the version is stored as an integer but the header uses a string, the match fails silently.
Memory grows over time. To keep performance and cost under control, agents prune old entries.
The pattern uses a background job that runs daily to remove entries older than a threshold.
# Pruning job: remove entries older than 90 days
psql -c "
DELETE FROM agent_memory
WHERE created_at < NOW() - INTERVAL '90 days'
AND id NOT IN (
SELECT id FROM agent_memory
WHERE updated_at >= NOW() - INTERVAL '30 days'
)
"
The job also rebuilds the vector index after pruning.
# Rebuild vector index
pg_dump -t agent_memory -f /backup/agent_memory.dump
psql -c "VACUUM ANALYZE agent_memory;"
Failure mode: The pruning job deletes entries that were recently updated. The query updated_at >= NOW() - INTERVAL '30 days' is critical: without it, the agent retains only the most recent 30 days of data.
Sharp edge: The VACUUM ANALYZE command is not optional. Without it, the database’s query planner does not use the index efficiently, leading to slow memory lookups.
Memory systems must be validated for consistency. This pattern runs periodic checks that verify:
A validation job runs weekly:
# Validate memory integrity
#!/bin/bash
echo "Starting memory integrity validation"
# Check vector embedding dimensions
psql -t -c "
SELECT COUNT(*) FROM agent_memory
WHERE vector_dims(embedding) != 1536
" | grep -q "0" || {
echo "ERROR: Vector dimension mismatch"
exit 1
}
# Check event log uniqueness
psql -t -c "
SELECT COUNT(*) FROM (
SELECT event_id, COUNT(*) FROM agent_events
GROUP BY event_id
HAVING COUNT(*) > 1
) AS duplicates
" | grep -q "0" || {
echo "ERROR: Duplicate event IDs"
exit 1
}
# Check snapshot checksums
find /snapshots -name "*.tar.gz" -exec md5sum {} \; > /tmp/snapshots.md5
diff /tmp/snapshots.md5 /etc/snapshots.md5 || {
echo "ERROR: Snapshot checksum mismatch"
exit 1
}
echo "Memory integrity validation succeeded"
Failure mode: The validation script assumes md5sum is available. On a system without md5sum, the checksum check fails silently.
Sharp edge: The psql -t flag outputs only the result text, no headers or formatting. The script must rely on exact output counts. A single row with value 1 means no errors; 0 means at least one failure.
Memory architecture is not a one-size-fits-all problem. The patterns described here—retrieval-driven loops, append-only logs, delta merging, versioning, hierarchical composition, conflict resolution, pruning, and validation—form a toolkit for building resilient, scalable, and intelligent memory systems. Each pattern addresses a specific operational need, and their interplay enables agents to remember, reason, and evolve over time.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy