---
name: agentic-memory-architect
description: Expert guide for long-term episodic memory integration (Mem0 v2, Letta/MemGPT, Zep v2), memory tier architecture, pgvector HNSW storage, and unified context management for autonomous AI agents / Panduan ahli untuk integrasi memori episodik jangka panjang (Mem0 v2, Letta/MemGPT, Zep v2), arsitektur tier memori, penyimpanan pgvector HNSW, dan manajemen konteks terpadu untuk agen AI otonom.
author: "Roedy Rustam"
version: "4.2.0"
---

# Agentic Memory Architect & Episodic Memory Guide

[English](#english) | [Bahasa Indonesia](#bahasa-indonesia)

---

<a name="english"></a>
## English

### Orchestration & Integration

Connects and orchestrates with:
- `multi-agent-orchestration` — shared memory state across agent swarms
- `pydantic-ai-expert` — type-safe memory injection into Pydantic AI agents
- `session-memory-manager` — short-term session checkpoint layer
- `vector-db-rag-expert` — pgvector/HNSW for long-term episodic retrieval
- `database-orm-expert` — Prisma/Drizzle schema for memory persistence
- `ai-llm-integration-expert` — context window injection at inference time
- `zero-to-prod-orchestrator` — memory layer provisioned in Phase 2 (Foundation)

---

### Purpose

Design and implement persistent, multi-tier memory systems for autonomous AI agents that transcend simple context windows. Enable agents to **remember users across sessions**, recall past decisions, maintain knowledge graphs, and operate with human-like episodic continuity — all while remaining within token budget constraints.

---

### Memory Tier Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                    AGENT MEMORY HIERARCHY                        │
├─────────────────────────────────────────────────────────────────┤
│  TIER 0 │ In-Context Working Memory (current conversation)       │
│         │ ≤ 128K tokens │ Lost on session end                   │
│         │ Implementation: Raw messages array in LLM call         │
├─────────────────────────────────────────────────────────────────┤
│  TIER 1 │ Session Memory (within-session graph)                  │
│         │ Hours to days │ Redis / Zep session store              │
│         │ Implementation: Zep v2 session + entity graph          │
├─────────────────────────────────────────────────────────────────┤
│  TIER 2 │ Episodic Memory (cross-session, user-scoped)           │
│         │ Weeks to months │ Mem0 + pgvector HNSW                 │
│         │ Implementation: Mem0 Memory.add() / Memory.search()    │
├─────────────────────────────────────────────────────────────────┤
│  TIER 3 │ Semantic / Procedural Memory (agent knowledge base)    │
│         │ Permanent │ pgvector + structured KB tables            │
│         │ Implementation: RAG pipeline over domain knowledge     │
└─────────────────────────────────────────────────────────────────┘
```

```mermaid
flowchart TD
    IN["User Input"] --> T0["Tier 0: In-Context\n(current messages)"]
    T0 --> AGENT["Agent LLM Call"]
    AGENT --> OUT["Response"]
    AGENT <-->|"recall + store"| T1["Tier 1: Zep Session\n(entity + temporal graph)"]
    AGENT <-->|"recall + store"| T2["Tier 2: Mem0 Episodic\n(cross-session user memories)"]
    AGENT <-->|"retrieve only"| T3["Tier 3: pgvector RAG\n(domain knowledge base)"]
    T1 -->|"promotes key facts"| T2
    T2 -->|"populates embeddings"| T3
```

---

### 1. Mem0 v2 — Cross-Session Episodic Memory

Mem0 provides a managed memory layer that extracts and stores entities, preferences, and facts from conversations, making them searchable across sessions.

```bash
pip install mem0ai
# OR for self-hosted:
pip install mem0ai[oss]
```

#### Python: Full CRUD Memory Operations

```python
import os
from mem0 import Memory

# Initialize with custom vector store (pgvector)
config = {
    "vector_store": {
        "provider": "pgvector",
        "config": {
            "dbname": "mem0_db",
            "collection_name": "agent_memories",
            "embedding_model_dims": 1536,
            "host": os.environ["PGVECTOR_HOST"],
            "port": 5432,
            "user": os.environ["PGVECTOR_USER"],
            "password": os.environ["PGVECTOR_PASSWORD"],
        },
    },
    "llm": {
        "provider": "anthropic",
        "config": {
            "model": "claude-sonnet-4-5",
            "api_key": os.environ["ANTHROPIC_API_KEY"],
        },
    },
    "embedder": {
        "provider": "openai",
        "config": {
            "model": "text-embedding-3-small",
            "api_key": os.environ["OPENAI_API_KEY"],
        },
    },
}

memory = Memory.from_config(config)

# ADD: Store memories from a conversation turn
def add_conversation_memory(user_id: str, messages: list[dict]) -> list[dict]:
    """
    Extract and store memories from a conversation.
    Mem0 automatically identifies entities, preferences, and facts.
    Returns list of memory entries created.
    """
    result = memory.add(
        messages=messages,
        user_id=user_id,
        metadata={"source": "conversation", "app": "my-agent"},
    )
    return result  # [{"id": "uuid", "memory": "User prefers TypeScript over Python", ...}]


# SEARCH: Retrieve relevant memories for the current query
def recall_memories(user_id: str, query: str, top_k: int = 5) -> list[dict]:
    """
    Vector-similarity search over user's episodic memories.
    Returns ranked list of relevant memories.
    """
    results = memory.search(
        query=query,
        user_id=user_id,
        limit=top_k,
    )
    return results  # [{"id": "...", "memory": "...", "score": 0.92, ...}]


# GET ALL: List all memories for a user
def get_all_memories(user_id: str) -> list[dict]:
    return memory.get_all(user_id=user_id)


# UPDATE: Correct or expand an existing memory
def update_memory(memory_id: str, new_data: str) -> dict:
    return memory.update(memory_id=memory_id, data=new_data)


# DELETE: Remove a specific memory
def delete_memory(memory_id: str) -> None:
    memory.delete(memory_id=memory_id)


# RESET: Clear all memories for a user (use with caution)
def reset_user_memories(user_id: str) -> None:
    memory.delete_all(user_id=user_id)


# --- AGENT LOOP INTEGRATION ---
async def agent_with_memory(user_id: str, user_input: str) -> str:
    import anthropic

    # 1. Recall relevant past memories
    memories = recall_memories(user_id, user_input, top_k=5)
    memory_context = "\n".join(
        f"- {m['memory']}" for m in memories if m.get("score", 0) > 0.7
    )

    system_prompt = f"""You are a helpful AI assistant.
You have the following relevant memories about this user:
{memory_context if memory_context else "No prior memories found."}
Use these memories to personalize your response."""

    client = anthropic.Anthropic()
    response = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=2048,
        system=system_prompt,
        messages=[{"role": "user", "content": user_input}],
    )
    assistant_reply = response.content[0].text

    # 2. Store this turn in Mem0 for future sessions
    add_conversation_memory(
        user_id,
        [
            {"role": "user", "content": user_input},
            {"role": "assistant", "content": assistant_reply},
        ],
    )

    return assistant_reply
```

#### TypeScript: Mem0 Client

```typescript
import MemoryClient from "mem0ai";

const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });

export interface Memory {
  id: string;
  memory: string;
  score?: number;
  created_at: string;
  updated_at: string;
}

export async function addMemory(
  userId: string,
  messages: Array<{ role: "user" | "assistant"; content: string }>,
): Promise<Memory[]> {
  const result = await mem0.add(messages, { user_id: userId });
  return result as Memory[];
}

export async function searchMemory(
  userId: string,
  query: string,
  limit = 5,
): Promise<Memory[]> {
  const results = await mem0.search(query, { user_id: userId, limit });
  return results as Memory[];
}

export async function deleteMemory(memoryId: string): Promise<void> {
  await mem0.delete(memoryId);
}

export async function getAllMemories(userId: string): Promise<Memory[]> {
  const result = await mem0.getAll({ user_id: userId });
  return result as Memory[];
}
```

---

### 2. Letta (MemGPT) — Unbounded In-Context Memory Management

Letta enables LLMs to manage their own memory by providing explicit memory blocks (`core_memory`, `archival_memory`) that the model can read/write through tool calls, effectively giving LLMs unlimited memory.

```bash
pip install letta-client
letta server start  # starts local Letta server on :8283
```

#### Python: Persisted Agent with Custom Memory

```python
from letta_client import Letta

client = Letta(base_url="http://localhost:8283")

# Create a persisted agent with custom memory blocks
agent = client.agents.create(
    name="research-assistant",
    model="anthropic/claude-sonnet-4-5",
    embedding="openai/text-embedding-3-small",
    memory_blocks=[
        {
            "label": "human",
            "value": "Name: Unknown\nPreferences: Unknown\nProjects: None yet.",
            "limit": 2000,
        },
        {
            "label": "persona",
            "value": (
                "I am a research assistant with persistent memory. "
                "I remember user preferences, past discussions, and project context "
                "across all sessions."
            ),
            "limit": 2000,
        },
    ],
    tools=["core_memory_append", "core_memory_replace", "archival_memory_insert",
           "archival_memory_search"],
)

print(f"Agent ID: {agent.id}")  # Save this for future sessions


def chat_with_persisted_agent(agent_id: str, message: str) -> str:
    """Send a message to a persisted Letta agent and get a response."""
    resp = client.agents.messages.create(
        agent_id=agent_id,
        messages=[{"role": "user", "content": message}],
    )
    # Extract the assistant text from the response
    for msg in resp.messages:
        if msg.message_type == "assistant_message":
            return msg.content
    return ""


def get_agent_memory(agent_id: str) -> dict:
    """Inspect current in-context memory blocks of the agent."""
    memory = client.agents.core_memory.retrieve(agent_id=agent_id)
    return {block.label: block.value for block in memory.memory.values()}


def search_archival_memory(agent_id: str, query: str) -> list[str]:
    """Search the agent's long-term archival memory store."""
    results = client.agents.archival_memory.list(agent_id=agent_id, query=query)
    return [r.text for r in results.archival_memory]
```

---

### 3. Zep v2 — Temporal Memory & User Knowledge Graph

Zep provides a fast memory service with temporal awareness, entity extraction, and user knowledge graph construction. It handles the Tier 1 (session) and bridges to Tier 2 (episodic).

```bash
pip install zep-cloud
# OR self-hosted:
docker run -p 8000:8000 ghcr.io/getzep/zep:latest
```

#### Python: Zep Session + Graph Memory

```python
import os
from zep_cloud.client import AsyncZep
from zep_cloud.types import Message, RoleType

zep = AsyncZep(api_key=os.environ["ZEP_API_KEY"])


async def ensure_user_and_session(user_id: str, session_id: str) -> None:
    """Idempotently create user and session in Zep."""
    try:
        await zep.user.add(user_id=user_id)
    except Exception:
        pass  # User already exists

    try:
        await zep.memory.add_session(
            session_id=session_id,
            user_id=user_id,
            metadata={"app": "my-agent", "version": "1.0"},
        )
    except Exception:
        pass  # Session already exists


async def store_turn(
    session_id: str,
    user_message: str,
    assistant_message: str,
) -> None:
    """Persist a conversation turn to Zep for memory extraction."""
    await zep.memory.add(
        session_id=session_id,
        messages=[
            Message(role_type=RoleType.UserRole, role="user", content=user_message),
            Message(role_type=RoleType.AssistantRole, role="assistant", content=assistant_message),
        ],
    )


async def recall_session_context(session_id: str) -> str:
    """Retrieve Zep's synthesized memory summary + relevant facts."""
    memory = await zep.memory.get(session_id=session_id)
    parts = []
    if memory.summary and memory.summary.content:
        parts.append(f"Session Summary:\n{memory.summary.content}")
    if memory.facts:
        facts_text = "\n".join(f"- {fact}" for fact in memory.facts[:10])
        parts.append(f"Key Facts:\n{facts_text}")
    return "\n\n".join(parts)


async def search_user_graph(user_id: str, query: str) -> list[dict]:
    """
    Search the user's temporal knowledge graph for entities and relations.
    Returns enriched context about what Zep knows about this user.
    """
    results = await zep.graph.search(
        user_id=user_id,
        query=query,
        scope="edges",       # 'nodes' | 'edges' — edges capture relations
        limit=10,
        reranker="rrf",      # Reciprocal Rank Fusion for hybrid search
    )
    return [
        {
            "source": edge.source_node_name,
            "relation": edge.relation,
            "target": edge.target_node_name,
            "fact": edge.fact,
            "created_at": edge.created_at.isoformat() if edge.created_at else None,
        }
        for edge in (results.edges or [])
    ]


async def add_structured_data_to_graph(user_id: str, data: str, data_type: str = "text") -> None:
    """
    Inject structured information (e.g., user profile JSON) into Zep's graph.
    Zep will extract entities and relations automatically.
    """
    await zep.graph.add(user_id=user_id, data=data, type=data_type)
```

---

### 4. pgvector + HNSW — Long-Term Episodic Storage

For Tier 2/3 storage, use PostgreSQL with pgvector and HNSW indexes for sub-millisecond similarity search over millions of memory embeddings.

#### SQL Schema

```sql
-- Enable pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;

-- Episodic memory table
CREATE TABLE agent_memories (
    id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id      TEXT NOT NULL,
    agent_id     TEXT,
    content      TEXT NOT NULL,
    embedding    VECTOR(1536) NOT NULL,  -- OpenAI text-embedding-3-small
    tier         SMALLINT NOT NULL DEFAULT 2,  -- 1=session, 2=episodic, 3=semantic
    source       TEXT NOT NULL DEFAULT 'conversation',
    metadata     JSONB NOT NULL DEFAULT '{}',
    created_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at   TIMESTAMPTZ              -- NULL = permanent
);

-- HNSW index for cosine similarity (outperforms IVFFlat at recall@10)
CREATE INDEX idx_memories_embedding_hnsw ON agent_memories
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

-- B-tree for user-scoped filtering
CREATE INDEX idx_memories_user_id ON agent_memories (user_id);

-- Composite for time-scoped recall
CREATE INDEX idx_memories_user_created ON agent_memories (user_id, created_at DESC);
```

#### TypeScript: pgvector Memory Repository

```typescript
import { Pool } from "pg";
import OpenAI from "openai";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export interface EpisodicMemory {
  id: string;
  userId: string;
  content: string;
  metadata: Record<string, unknown>;
  createdAt: Date;
  similarity?: number;
}

async function embed(text: string): Promise<number[]> {
  const resp = await openai.embeddings.create({
    model: "text-embedding-3-small",
    input: text,
  });
  return resp.data[0].embedding;
}

export async function storeMemory(
  userId: string,
  content: string,
  metadata: Record<string, unknown> = {},
  tier: 1 | 2 | 3 = 2,
): Promise<string> {
  const embedding = await embed(content);
  const vectorLiteral = `[${embedding.join(",")}]`;

  const result = await pool.query<{ id: string }>(
    `INSERT INTO agent_memories (user_id, content, embedding, tier, metadata)
     VALUES ($1, $2, $3::vector, $4, $5)
     RETURNING id`,
    [userId, content, vectorLiteral, tier, JSON.stringify(metadata)],
  );

  return result.rows[0].id;
}

export async function recallMemories(
  userId: string,
  query: string,
  topK = 5,
  minSimilarity = 0.70,
): Promise<EpisodicMemory[]> {
  const embedding = await embed(query);
  const vectorLiteral = `[${embedding.join(",")}]`;

  const result = await pool.query<EpisodicMemory & { similarity: number }>(
    `SELECT
       id,
       user_id AS "userId",
       content,
       metadata,
       created_at AS "createdAt",
       1 - (embedding <=> $1::vector) AS similarity
     FROM agent_memories
     WHERE user_id = $2
       AND (expires_at IS NULL OR expires_at > NOW())
       AND 1 - (embedding <=> $1::vector) >= $3
     ORDER BY embedding <=> $1::vector
     LIMIT $4`,
    [vectorLiteral, userId, minSimilarity, topK],
  );

  return result.rows;
}

export async function deleteExpiredMemories(): Promise<number> {
  const result = await pool.query(
    `DELETE FROM agent_memories WHERE expires_at IS NOT NULL AND expires_at <= NOW()`,
  );
  return result.rowCount ?? 0;
}
```

---

### 5. Memory Injection Template — RAG Pipeline Integration

```typescript
import { recallMemories } from "./memory-repository";
import { searchMemory } from "./mem0-client";

export async function buildMemoryAugmentedPrompt(
  userId: string,
  userInput: string,
  systemBase: string,
): Promise<{ system: string; injectedMemoryCount: number }> {
  // Parallel recall from Tier 2 (pgvector) and Mem0
  const [pgMemories, mem0Memories] = await Promise.all([
    recallMemories(userId, userInput, 5, 0.72),
    searchMemory(userId, userInput, 5),
  ]);

  const allMemories = [
    ...pgMemories.map((m) => ({ content: m.content, score: m.similarity ?? 0, source: "episodic" })),
    ...mem0Memories
      .filter((m) => (m.score ?? 0) > 0.7)
      .map((m) => ({ content: m.memory, score: m.score ?? 0, source: "mem0" })),
  ]
    .sort((a, b) => b.score - a.score)
    .slice(0, 8);                         // Top 8 memories across both sources

  const memoryBlock =
    allMemories.length > 0
      ? `\n\n## Relevant Memories About This User\n${allMemories
          .map((m) => `- [${m.source}] ${m.content}`)
          .join("\n")}`
      : "";

  return {
    system: `${systemBase}${memoryBlock}`,
    injectedMemoryCount: allMemories.length,
  };
}
```

---

### 6. Memory CRUD — Full TypeScript Interface

```typescript
// memory-manager.ts — unified interface over Mem0 + pgvector
import { addMemory, searchMemory, getAllMemories, deleteMemory } from "./mem0-client";
import { storeMemory, recallMemories } from "./memory-repository";

export type MemoryBackend = "mem0" | "pgvector" | "both";

export async function createMemory(
  userId: string,
  content: string,
  backend: MemoryBackend = "both",
  metadata: Record<string, unknown> = {},
): Promise<{ mem0Ids?: string[]; pgvectorId?: string }> {
  const results: { mem0Ids?: string[]; pgvectorId?: string } = {};

  if (backend === "mem0" || backend === "both") {
    const mem0Result = await addMemory(userId, [
      { role: "user", content: `Remember this: ${content}` },
    ]);
    results.mem0Ids = mem0Result.map((m) => m.id);
  }

  if (backend === "pgvector" || backend === "both") {
    results.pgvectorId = await storeMemory(userId, content, metadata);
  }

  return results;
}

export async function queryMemory(
  userId: string,
  query: string,
  backend: MemoryBackend = "both",
): Promise<Array<{ content: string; score: number; source: string }>> {
  const results: Array<{ content: string; score: number; source: string }> = [];

  if (backend === "mem0" || backend === "both") {
    const mem0Results = await searchMemory(userId, query, 5);
    results.push(
      ...mem0Results.map((m) => ({
        content: m.memory,
        score: m.score ?? 0.5,
        source: "mem0",
      })),
    );
  }

  if (backend === "pgvector" || backend === "both") {
    const pgResults = await recallMemories(userId, query, 5);
    results.push(
      ...pgResults.map((m) => ({
        content: m.content,
        score: m.similarity ?? 0.5,
        source: "pgvector",
      })),
    );
  }

  return results.sort((a, b) => b.score - a.score).slice(0, 10);
}

export async function purgeUserMemory(
  userId: string,
  backend: MemoryBackend = "both",
): Promise<void> {
  const { Pool } = await import("pg");
  const pool = new Pool({ connectionString: process.env.DATABASE_URL });

  const tasks: Promise<void>[] = [];

  if (backend === "mem0" || backend === "both") {
    const { default: MemoryClient } = await import("mem0ai");
    const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });
    tasks.push(mem0.deleteAll({ user_id: userId }).then(() => undefined));
  }

  if (backend === "pgvector" || backend === "both") {
    tasks.push(
      pool
        .query("DELETE FROM agent_memories WHERE user_id = $1", [userId])
        .then(() => undefined),
    );
  }

  await Promise.all(tasks);
}
```

---

<a name="bahasa-indonesia"></a>
## Bahasa Indonesia

### Integrasi Orkestrasi

Terhubung dan mengorkestrasi bersama:
- `multi-agent-orchestration` — state memori bersama di seluruh agent swarm
- `pydantic-ai-expert` — injeksi memori type-safe ke agen Pydantic AI
- `session-memory-manager` — lapisan checkpoint sesi jangka pendek
- `vector-db-rag-expert` — pgvector/HNSW untuk retrieval episodik jangka panjang
- `database-orm-expert` — skema Prisma/Drizzle untuk persistensi memori
- `ai-llm-integration-expert` — injeksi context window saat inferensi
- `zero-to-prod-orchestrator` — lapisan memori diprovisioning di Phase 2 (Foundation)

---

### Tujuan

Merancang dan mengimplementasikan sistem memori multi-tier yang persisten untuk agen AI otonom yang melampaui context window sederhana. Memungkinkan agen untuk **mengingat pengguna lintas sesi**, memanggil keputusan masa lalu, mempertahankan knowledge graph, dan beroperasi dengan kontinuitas episodik menyerupai manusia — sambil tetap dalam batas anggaran token.

---

### Arsitektur Tier Memori

| Tier | Nama | Durasi | Teknologi | Kapasitas |
|------|------|---------|-----------|-----------|
| 0 | In-Context Working Memory | Sesi saat ini | Array messages LLM | ≤ 128K token |
| 1 | Session Memory | Jam hingga hari | Zep v2 session + entity graph | Tak terbatas (ringkasan) |
| 2 | Episodic Memory | Minggu hingga bulan | Mem0 + pgvector HNSW | Jutaan memori |
| 3 | Semantic / Procedural | Permanen | pgvector RAG + tabel KB terstruktur | Basis pengetahuan domain |

---

### 1. Mem0 v2 — Memori Episodik Lintas Sesi

Mem0 menyediakan lapisan memori terkelola yang mengekstrak dan menyimpan entitas, preferensi, dan fakta dari percakapan, sehingga dapat dicari lintas sesi.

**Konsep kunci:**
- `Memory.add(messages, user_id)`: ekstrak dan simpan memori dari percakapan — Mem0 secara otomatis mengidentifikasi entitas (nama, preferensi, kendala proyek)
- `Memory.search(query, user_id, limit)`: pencarian vektor-kemiripan atas memori episodik pengguna — kembalikan daftar berperingkat fakta yang relevan
- **Multi-user isolation**: setiap memori terikat pada `user_id` — tidak ada kebocoran antar pengguna
- **Entity extraction**: Mem0 menggunakan LLM untuk mengekstrak entitas terstruktur (nama, preferensi, batasan) dari percakapan mentah

**Dukungan backend:** Mem0 Cloud (API), atau self-hosted dengan pgvector, Qdrant, Pinecone, atau Chroma.

Lihat implementasi lengkap Python dan TypeScript di [seksi English](#english).

---

### 2. Letta/MemGPT — Manajemen Memori In-Context Tak Terbatas

Letta memungkinkan LLM mengelola memori sendiri melalui tool calls eksplisit ke blok memori:
- **`core_memory_append`**: tambahkan fakta baru ke blok memori inti (persona, human profile)
- **`core_memory_replace`**: perbarui fakta yang sudah ada di memori inti
- **`archival_memory_insert`**: simpan informasi ke penyimpanan arsip jangka panjang (vektor)
- **`archival_memory_search`**: cari arsip menggunakan pencarian semantik

**Pola penggunaan:**
1. Buat agen persisten dengan `client.agents.create()` — simpan `agent_id`
2. Gunakan `agent_id` yang sama di semua sesi masa depan — agen ingat konteks sebelumnya
3. Agen secara proaktif mengelola memorinya sendiri melalui tool calls saat konteks penuh

---

### 3. Zep v2 — Memori Temporal & Knowledge Graph Pengguna

Zep menyediakan layanan memori cepat dengan kesadaran temporal, ekstraksi entitas, dan konstruksi knowledge graph pengguna.

**Fitur utama:**
- **Temporal memory**: setiap fakta dicap waktu — agen dapat mempertanyakan "apa yang pengguna katakan minggu lalu tentang X?"
- **Entity graph**: Zep secara otomatis membangun graf pengetahuan entitas dari percakapan (orang, tempat, preferensi, keputusan)
- **`graph.add()`**: injeksikan data terstruktur (JSON profil pengguna) ke grafik — Zep mengekstrak entitas dan relasi
- **`graph.search(scope="edges")`**: cari relasi di knowledge graph menggunakan hybrid search (vektor + kata kunci) + RRF reranking
- **Session summary**: Zep secara otomatis merangkum sesi panjang menjadi ringkasan yang dapat diinjeksikan ke konteks agen

---

### 4. pgvector + HNSW — Penyimpanan Episodik Jangka Panjang

Untuk penyimpanan Tier 2/3, gunakan PostgreSQL dengan pgvector dan indeks HNSW untuk pencarian kemiripan sub-milidetik atas jutaan embedding memori.

**Mengapa HNSW daripada IVFFlat?**
- HNSW: recall@10 = 99%+, latensi stabil tanpa perlu `VACUUM`
- IVFFlat: recall@10 = 95%, memerlukan pemilihan `nlist` yang tepat dan `VACUUM` rutin

**Parameter HNSW yang direkomendasikan:**
- `m = 16`: koneksi per lapisan — keseimbangan antara performa recall dan penggunaan memori
- `ef_construction = 64`: kualitas indeks saat build — nilai lebih tinggi = recall lebih baik, build lebih lambat
- `ef_search = 40` (opsional via `SET hnsw.ef_search = 40`): kualitas pencarian saat query

---

### 5. Template Injeksi Memori ke RAG Pipeline

**Alur retrieval memori optimal:**

```
Query pengguna
    ↓
[Parallel Recall]
    ├── pgvector HNSW search (Tier 2 episodic)
    └── Mem0 semantic search (cross-session)
    ↓
[Merge & Rerank by similarity score]
    ↓
[Filter: min_similarity = 0.70, top_k = 8]
    ↓
[Inject ke system prompt sebagai "Relevant Memories" block]
    ↓
LLM call → response
    ↓
[Store turn to Mem0 + pgvector async]
```

---

### Anti-Slop Checklist Memori

- [ ] `user_id` selalu unik dan konsisten antar sesi — gunakan UUID deterministik (mis. `uuid5(NAMESPACE_DNS, email)`)
- [ ] Minimal similarity threshold 0.70 untuk menghindari injeksi memori tidak relevan
- [ ] Memori yang disimpan tidak melebihi `expires_at` — implementasikan TTL untuk memori sementara
- [ ] Zep session dibuat sebelum `memory.add()` dipanggil — validasi idempoten
- [ ] Letta `agent_id` disimpan di database untuk reuse lintas sesi
- [ ] Purge memori pengguna tersedia untuk kepatuhan GDPR/PDPA
- [ ] Semua operasi memori async — tidak memblokir response agen
- [ ] Indeks HNSW di-`REINDEX` setelah lebih dari 10% baris dihapus
