Dulu, waktu pertama kali bikin agent AI, cara menambahkan "memori" terasa sepele: potong beberapa giliran obrolan terakhir (chat_history[-5:]) lalu tempelkan ke system prompt. Selama demonya masih satu sesi dan satu topik, cara itu jalan sempurna.
Masalahnya datang belakangan, saat satu pengguna sudah mengobrol di ratusan sesi selama berbulan-bulan. Jendela konteks nggak cukup, dan biaya token-nya naik terus kalau semua riwayat ikut dikirim. Jalan keluar paling populer di industri tuh melempar seluruh rekaman percakapan lama ke vector database — Pinecone, Qdrant, Chroma, atau apa pun yang kamu pakai. Setiap ada pesan baru, agent mencari potongan teks yang paling mirip maknanya lewat semantic search, alias perbandingan cosine similarity. Potongan yang dianggap relevan itu lalu disisipkan ke prompt.
Sampai di sini semuanya terasa beres. Tapi kalau kamu pernah melihat agent-mu menjawab ngawur justru saat informasinya ada di database, misalnya menyebut kamu masih di pekerjaan lama padahal kamu sudah pindah, itu bukan bug prompt. Itu batas arsitektur.
Poin Penting
- Vector store murni gagal saat fakta berubah, karena pencarian kemiripan makna nggak menyimpan informasi soal kapan sebuah fakta masih berlaku dan kapan sudah kedaluwarsa.
- Memori agent modern dibagi tiga lapisan: working memory di jendela konteks, semantic memory di vector store untuk pengetahuan tak terstruktur, dan episodic/relational memory di graf entitas bertanggal.
- Temporal knowledge graph menyimpan fakta sebagai relasi berentang waktu, lalu menandai fakta lama sebagai kedaluwarsa tanpa menghapus riwayatnya.
- Sejak 2026, graph store dihapus dari paket open source Mem0 dan pindah jadi fitur bawaan Platform-nya, sementara Graphiti tetap jadi jalur open source untuk graf temporal penuh.
- Klaim akurasi di halaman vendor berasal dari benchmark mereka sendiri, jadi angkanya lebih layak dibaca sebagai petunjuk arah.
Kenapa vector store gagal saat fakta berubah
Pencarian vector sangat andal dalam menemukan kemiripan topik. Sayangnya ia buta terhadap waktu. Ia juga nggak tahu bahwa satu fakta bisa membatalkan fakta lain yang mirip.
Lihat urutan sesi ini, nih. Misalkan pengguna agent-nya bernama Andi:
- Sesi 1 (Januari 2026) — Andi bilang: "Aku baru pindah kerja ke Startup A sebagai Full-Stack Engineer." Tersimpan sebagai potongan vector A.
- Sesi 2 (Agustus 2026) — Andi bilang: "Sekarang aku memimpin tim developer di Startup B." Tersimpan sebagai potongan vector B.
- Sesi 3 (September 2026) — Andi bertanya: "Aku sekarang kerja di mana, dan perannya apa?"
Sekarang kita perhatikan apa yang terjadi di sesi ketiga. Pertanyaan itu punya kemiripan makna yang hampir sama dengan potongan A maupun potongan B, karena keduanya memang membicarakan pekerjaan Andi. Kedua potongan tadi lalu masuk ke konteks secara bersamaan. Model yang menerimanya nggak punya alasan untuk menolak salah satunya, jadi jawaban yang keluar campur aduk: "Anda bekerja di Startup A dan Startup B sebagai Full-Stack Lead Developer."
Vector store murni memang nggak punya cara untuk tahu bahwa fakta B menggantikan fakta A. Nggak ada struktur relasi, nggak ada garis waktu, dan nggak ada status yang bilang mana yang masih berlaku. Yang ia punya cuma jarak antar-embedding. Jarak itu nggak menyimpan informasi soal waktu sama sekali.
Pertanyaan yang gagal dijawab di sini bukan "fakta apa yang mirip dengan pertanyaan ini?". Pertanyaan yang gagal dijawab adalah yang ini: "dari semua yang aku tahu soal orang ini, mana yang masih benar sekarang?"
Bunyinya sih sederhana, tapi menjawabnya butuh tempat penyimpanan yang lebih dari sekadar embedding. Strukturnya harus bisa bilang "ini benar sejak kapan, sampai kapan".
Tiga lapisan memori agent
Untuk menjawab pertanyaan semacam itu, sistem memori agent modern membagi penyimpanannya ke dalam tiga lapisan yang saling melengkapi. Kalau kamu sudah paham bedanya short-term dan long-term memory, bagian ini aman dilewati. Langsung ke bagian graf di bawah aja.
1. Working memory adalah konteks yang sedang dipakai saat ini. Isinya giliran pesan terbaru, dikelola pakai buffer percakapan, sliding window, atau peringkasan berkala supaya nggak melampaui jendela konteks. Umurnya paling pendek, dan memang begitu seharusnya.
2. Semantic memory adalah kumpulan pengetahuan faktual yang relatif statis: dokumen SOP, artikel wiki, arsip panduan internal. Isinya nggak berubah karena percakapan. Pencarian berbasis makna di vector store memang pas di sini.
3. Episodic & relational memory adalah lapisan tempat kejadian dan hubungan antar-entitas disimpan. Di sinilah perubahan nasib pengguna dicatat: ia bekerja di mana, sejak kapan, sampai kapan. Lapisan ini butuh struktur, bukan sekadar tumpukan embedding. Strukturnya berupa graf, dan graf itulah yang bikin waktu bisa diperlakukan sebagai data.
Bentuk memori yang mengingat waktu
Di dalam temporal knowledge graph, memori disimpan sebagai relasi bertanggal. Bentuknya kira-kira seperti ini di Graphiti, engine graf yang jalan di atas Neo4j atau FalkorDB:
(:Entity {name: "Andi"})-[:RELATES_TO {
name: "WORKS_AT",
fact: "Andi bekerja di Startup A sebagai Full-Stack Engineer",
valid_at: "2026-01-01T00:00:00Z",
invalid_at: "2026-07-31T00:00:00Z"
}]->(:Entity {name: "Startup A"})
(:Entity {name: "Andi"})-[:RELATES_TO {
name: "WORKS_AT",
fact: "Andi memimpin tim developer di Startup B",
valid_at: "2026-08-01T00:00:00Z",
invalid_at: null
}]->(:Entity {name: "Startup B"})
Ini versi yang disederhanakan, ya. Setiap edge di Graphiti juga menyimpan jejak kapan ia kedaluwarsa di sistem, dan episode percakapan mana yang jadi sumbernya.
Yang penting buat kita bukan sintaks Cypher-nya, tapi tiga sifat yang muncul dari struktur itu.
Pertama, retrieval-nya jadi terarah. Ketika Andi bertanya soal pekerjaannya sekarang, sistem nggak perlu menebak mana yang relevan. Ia tinggal menelusuri relasi WORKS_AT milik Andi dan mengambil yang belum punya invalid_at.
Kedua, fakta lama nggak dihapus. Edge Startup A tetap ada lengkap dengan tanggal berakhirnya. Agent masih bisa menjawab "dulu Andi kerja di mana?" tanpa kehilangan riwayat. Beda mendasar dari pendekatan yang menimpa (overwrite) nilai lama.
Ketiga, waktu jadi bagian dari data, bukan metadata yang menempel di pinggir. Karena setiap fakta punya rentang berlaku, pertanyaan "mana yang masih benar" akhirnya bisa dijawab dengan operasi biasa. Tidak perlu menebak lewat model.
Peta perkakasnya, dan apa yang berubah di 2026
Jadi perkakasnya gimana? Kita punya dua framework besar yang mengotomatisasi pola ini, dan keduanya mengambil jalan berbeda dalam menghadapi fakta yang bertentangan.
Bedanya kelihatan dari arah alirannya: Mem0 menyelesaikan kontradiksi di sisi retrieval, Graphiti di sisi penyimpanan.
Mem0 awalnya menempuh jalur ekstraksi dan pembaruan. Setiap pasangan pesan baru, LLM mengekstrak fakta kandidat. Fase kedua membandingkan fakta itu dengan memori lama, lalu memilih satu dari empat operasi: ADD, UPDATE, DELETE, atau NOOP. Dukungan graf opsional dipasang ke graph database eksternal seperti Neo4j. Kalau kamu membaca tutorial Mem0 yang beredar di blog-blog, hampir semuanya menggambarkan arsitektur versi itu.
Empat operasi itu terdengar rapi. Kok bisa arahnya berubah? Versi terbaru algoritmanya cuma melakukan satu operasi: ADD, satu kali panggilan LLM, tanpa UPDATE maupun DELETE sama sekali. Ketika informasinya berubah, kedua fakta tetap disimpan lengkap dengan konteks waktunya. Yang mengurutkan mana yang paling relevan adalah retrieval-nya, bukan proses penulisan. Retrieval-nya sendiri kini hibrida: skor semantik, kata kunci (BM25), dan kecocokan entitas digabung jadi satu angka.
Sejak versi itu pula, graph_store dihapus dari paket open source Mem0. Graf sekarang dibangun otomatis di sisi Platform dari entitas yang sama-sama disebut antar-memori, jadi urusan menyiapkan Neo4j sendiri sudah hilang. Tapi graf itu menyambungkan memori lewat entitas yang kebetulan beririsan, bukan lewat relasi bertipe dengan rentang berlaku. Nggak ada valid_at atau invalid_at yang bisa kamu telusuri.
Graphiti mengambil jalur sebaliknya, dan lebih dekat ke model yang kita bahas di artikel ini. Ia menyimpan setiap fakta sebagai edge yang punya rentang berlaku ganda: kapan fakta itu benar di dunia nyata, dan kapan ia tercatat di sistem. Ketika ada informasi baru yang bertentangan, fakta lama ditandai kedaluwarsa lewat invalid_at, bukan dihapus. Setiap fakta juga masih bisa ditelusuri balik ke episode percakapan asalnya. Graphiti jalan sendiri di Neo4j atau FalkorDB, sedangkan versi terkelolanya adalah Zep.
Dua-duanya paket Apache-2.0 dan sama-sama ramai dipakai. Diperiksa 17 Oktober 2026, repo Mem0 ada di sekitar 67 ribu bintang dan Graphiti sekitar 31 ribu.
Satu catatan soal angka, dan ini berlaku untuk semua perkakas di daftar ini ⚠️: klaim peningkatan akurasi yang beredar di halaman masing-masing vendor berasal dari benchmark mereka sendiri. Angka itu boleh jadi petunjuk arah, tapi bukan hasil pengukuran independen.
Contoh: menaruh memory layer di depan FastAPI
Yuk lihat bentuknya di backend FastAPI. Cuplikan ini memakai API Mem0 OSS yang berlaku sekarang (paket mem0ai 2.x). Versinya penting, karena beberapa hal berubah dari tutorial-tutorial lama:
from fastapi import FastAPI
from pydantic import BaseModel
from mem0 import Memory
app = FastAPI(title="Production Agent Memory Service")
# Konfigurasi OSS sekarang cuma memegang vector store, LLM, embedder, dan reranker.
# `graph_store` dan `enable_graph` sudah dihapus dari paket open source.
config = {
"vector_store": {
"provider": "qdrant",
"config": {"host": "localhost", "port": 6333},
},
}
memory = Memory.from_config(config)
# call_llm: helper pemanggil LLM milikmu. Di sini sengaja nggak ditulis detail,
# karena yang sedang dibahas cuma lalu lintas memori di sekelilingnya.
class ChatRequest(BaseModel):
user_id: str
message: str
@app.post("/chat")
async def chat_endpoint(req: ChatRequest):
# 1. Ambil fakta yang relevan. Entity ID masuk lewat `filters`, bukan kwarg
# teratas seperti di versi lama -- yang lama sekarang melempar ValueError.
hasil = memory.search(
req.message,
filters={"user_id": req.user_id},
top_k=5,
)
# 2. Bentuk konteksnya. search() mengembalikan dict dengan key "results".
fakta = "\n".join(f"- {m['memory']}" for m in hasil["results"])
system_prompt = (
"Anda adalah AI Assistant. Informasi terkini tentang pengguna:\n" + fakta
)
# 3. Panggil LLM utama dengan prompt yang sudah diperkaya fakta.
llm_response = await call_llm(system_prompt=system_prompt, user_message=req.message)
# 4. Rekam giliran ini. Di add(), user_id masih diterima sebagai kwarg teratas.
memory.add(
[
{"role": "user", "content": req.message},
{"role": "assistant", "content": llm_response},
],
user_id=req.user_id,
)
return {"response": llm_response}
Kalau kamu menyalin dari tutorial lama, tiga hal ini yang paling sering bikin kaget, deh. Pertama, user_id yang dikirim sebagai argumen teratas di search() sekarang melempar error. Kedua, nama parameternya top_k, bukan limit. Ketiga, hasilnya dibungkus dalam key results, bukan list telanjang.
Satu hal lagi yang perlu disadari: konfigurasi di atas cuma punya memori semantik. Pertanyaan "mana yang masih berlaku" tetap harus dijawab di atasnya, entah dengan menyimpan metadata sendiri lalu memfilternya, atau dengan memakai engine yang memang menyimpan rentang waktu di tiap fakta. Di situ pembagian tugasnya kelihatan: vector store untuk menemukan hal yang mirip, graf temporal untuk memutuskan hal yang masih benar.
Memori yang hidup menghasilkan agent yang relevan
Membangun agent AI yang benar-benar personal bukan perlombaan memperbesar jendela konteks. Yang menentukan kualitasnya adalah seberapa pintar arsitektur sistem memfilter, memperbarui, dan melupakan informasi yang sudah kedaluwarsa. Dan bagian melupakan itu, anehnya, yang paling sering dilupakan 😅
Begitu kecepatan pencarian semantik digabung dengan ketepatan relasi bertanggal, agent-mu nggak cuma mengingat kata-kata pengguna. Ia mulai bisa membedakan apa yang dulu benar dan apa yang benar sekarang 👋