Architektura pamięci agentów AI: checkpointy i magazyny wektorowe
Tłumaczenie automatyczne Ten artykuł został automatycznie przetłumaczony z angielskiego oryginału.
Aktualizacja artykułu
Pierwotnie opublikowano 14 lutego 2026 r. Zaktualizowano i poddano przeglądowi 6 września 2026 r. Aktualizacja obejmuje kompaktowanie kontekstu, benchmarki pamięci oraz API magazynów, a także wyjaśnia różnice między stanem roboczym, checkpointami i pamięcią długoterminową.
Pętla rozumowania przetrwa tylko jedno żądanie, chyba że jej stan zostanie zapisany poza workerem. Bez pamięci agenta agent nie może wznowić wstrzymanego planu, odzyskać działania po awarii ani przypomnieć sobie preferencji z wcześniejszej sesji. Część 1 omawiała przepływ sterowania. W tym wpisie identyfikuję, jakiego stanu potrzebuje każda kolejna tura i gdzie ten stan powinien się znajdować.
Jako punkt odniesienia dla omówienia hot checkpointów wykorzystam Market Analyst Agent — niewielkiego agenta LangGraph, który pobiera dane rynkowe i tworzy raport analityczny. Sekcje dotyczące cold vectorów i surowego Markdownu to niezależne, ilustracyjne projekty pokazujące rozszerzenia, których obecny projekt jeszcze nie implementuje. Następnie omówię, kiedy warto użyć PostgreSQL, Redis, Qdrant, magazynów klucz-wartość i zwykłych plików Markdown.
Każdy z opisanych niżej magazynów jest odczytywany przez harness — kod sterujący pętlą wokół modelu. To harness decyduje, które elementy ich zawartości trafią do context window; magazyny same o tym nie decydują. Ten artykuł dotyczy miejsca przechowywania stanu, zanim harness po niego sięgnie. Część 3 i Część 4 omawiają, co harness robi następnie z promptem.
Czym jest pamięć agenta AI?
Pamięć agenta AI to warstwa stanu, która pozwala agentowi zachowywać postęp zadania, pobierać wcześniejszą wiedzę i aktualizować to, co wie, między kolejnymi uruchomieniami. Projekt może wykorzystywać checkpointy, magazyny semantyczne lub ustrukturyzowane oraz dokumenty czytelne dla człowieka. Wybieraj tylko te magazyny, które są potrzebne z punktu widzenia wymagań produktu dotyczących odtwarzania i odzyskiwania stanu.
| Potrzeba | Najlepszy domyślny wybór | Dlaczego |
|---|---|---|
| Wstrzymanie i wznowienie jednego uruchomienia | Magazyn checkpointów PostgreSQL | Trwały, odpytywalny i łatwy w obsłudze wraz z danymi aplikacji |
| Nietrwały stan o niskich opóźnieniach | Magazyn checkpointów Redis | Szybkie wznowienie i krótkotrwały stan, z kompromisami dotyczącymi trwałości |
| Semantyczne przypominanie między wątkami | Qdrant lub pgvector | Pobiera wspomnienia według znaczenia, a nie tylko dokładnych kluczy |
| Ustrukturyzowane fakty o użytkowniku | PostgreSQL lub magazyn klucz-wartość | Deterministyczne aktualizacje są lepsze niż rozmyte wyszukiwanie preferencji i ID |
| Konwencje projektowe i poznane procedury | Pliki Markdown lub JSON | Czytelne dla człowieka, obsługują diffy i łatwe do aktualizowania przez agentów |
| Pamięć relacji między wieloma encjami | Graf wiedzy | Przydatny, gdy relacje są ważniejsze niż pojedyncze fakty |
Nie zaczynaj od pamięci tylko dlatego, że brzmi inteligentnie. Zacznij od widocznej dla użytkownika awarii: utraty postępu, zapomnienia preferencji, powtarzania badań lub braku możliwości ponownego wykorzystania konwencji projektowej.
Awarie wymagające pamięci
Agent bez stanu może odpowiedzieć na izolowane pytanie, ale zapomina żądanie natychmiast po zakończeniu wywołania. Taki projekt zawodzi, gdy produkt musi obsługiwać którekolwiek z poniższych zachowań:
- Wstrzymywanie i wznawianie: użytkownik rozpoczyna zadanie badawcze, zamyka laptopa i wraca następnego dnia. Bez stanu zapisanego w checkpointach agent zacznie od początku.
- Spójność w wielu turach: podczas długiej rozmowy agent musi pamiętać, z jakich narzędzi korzystał, jakie dane zebrał i które kroki planu ukończył.
- Personalizacja: powracający użytkownik oczekuje, że agent będzie znał jego tolerancję ryzyka, preferowaną głębokość analizy i wcześniejsze interakcje.
- Human-in-the-loop (HITL): agent zbiera dowody i czeka na zgodę człowieka przed wykonaniem kolejnego kroku. Stan oczekiwania musi przetrwać restart procesu.
W Market Analyst Agent z Części 1 żądanie „Analyze NVDA” generuje plan, pięć wywołań narzędzi, zebrane dane i szkic raportu. Gdy użytkownik odpowiada „wygląda dobrze, ale dodaj analizę konkurencji”, checkpoint odtwarza plan i badania z ostatniego ukończonego kroku. Dodanie kroku dotyczącego konkurencji wymagałoby interpretacji kolejnej wiadomości i ponownego planowania; towarzyszący projekt nie implementuje tego zachowania. Checkpoint dostarcza wcześniejszy stan, ale aplikacja musi zdecydować, jak nowe żądanie zmienia plan.
Pamięć długoterminowa obsługuje inny przypadek. Jeśli użytkownik wróci tydzień później i poprosi: „Update my NVDA analysis”, agent może potrzebować przypomnienia sobie preferencji dotyczącej konserwatywnych ocen ryzyka oraz zainteresowania akcjami spółek półprzewodnikowych. Magazyn pamięci oparty na wektorach może pobrać te fakty między sesjami bez ponownego pytania o nie.
Przykłady implementacji poniżej używają LangGraph, biblioteki open source LangChain do budowania agentów jako jawnych grafów stanu; wyznaczone przez nią granice magazynowania można uogólnić na dowolny framework. Pomyśl o trwającej rozmowie użytkownika „Analyze NVDA” jako o jednym wątku. Każde uruchomienie grafu w celu udzielenia odpowiedzi lub kontynuowania rozmowy jest jednym uruchomieniem w tym wątku. Gdy uruchomienie jest aktywne, kontekst modelu i lokalne zmienne programu stanowią jego pamięć roboczą; znikają, gdy praca zostaje zatrzymana. LangGraph nazywa stan zapisany dla tego jednego wątku pamięcią krótkoterminową, a fakty dostępne dla innych wątków — pamięcią długoterminową. Dalej „wątek” i „rozmowa” oznaczają to samo. Część 5 używa terminu „sesja” dla trwałego dziennika jednego uruchomienia, dlatego w tym artykule unikam go w odniesieniu do rozmowy.
Taksonomia pamięci agentów AI
Przed przejściem do implementacji warto sklasyfikować to, co agenci muszą pamiętać. Framework CoALA — Cognitive Architectures for Language Agents (Sumers, Yao i in., 2023) — to szeroko cytowana taksonomia oparta na kognitywistyce. Zakres pamięci wprowadziłem w moim wpisie o inżynierii kontekstu; tutaj rozwijam go do sześciu kategorii:
| Typ pamięci | Zakres | Czas życia | Przykład | Wzorzec magazynowania |
|---|---|---|---|---|
| Robocza | Bieżący krok | Milisekundy | Argumenty wywołania narzędzia, bieżąca odpowiedź LLM | W procesie (słownik Python) |
| Krótkoterminowa | Bieżący wątek | Minuty–godziny | Historia rozmowy, postęp planu, zebrane dane | Magazyn checkpointów |
| Epizodyczna | Między wątkami | Dni–miesiące | „W zeszłym tygodniu użytkownik pytał o wyniki NVDA” | Magazyn wektorowy / magazyn KV |
| Semantyczna | Między wątkami | Miesiące–bezterminowo | „Użytkownik preferuje konserwatywne inwestycje” | Magazyn wektorowy / magazyn KV |
| Dokumentowa | Między wątkami | Dni–bezterminowo | Notatki projektowe, podsumowania badań, poznane wzorce | Magazyn plików (Markdown/JSON) |
| Proceduralna | Cały system | Bezterminowo | „Podczas analizy akcji zawsze sprawdzaj dokumenty SEC” | Konfiguracja / prompt systemowy |
Pamięć robocza przechowuje bieżące obserwacje, pobrane fakty i wyniki pośrednie używane przez aktywne uruchomienie. Część z nich znajduje się w zmiennych aplikacji; wybrane wiadomości oraz wyniki narzędzi tworzą wejście modelu. To wejście musi zmieścić się w context window modelu, natomiast stan aplikacji może być większy i przetrwać wiele kroków. Pamięć procesu jest tracona podczas awarii, chyba że zostanie jawnie zapisana. Pozostałe warstwy dostarczają informacji do tego stanu roboczego.
Pamięć krótkoterminowa to checkpoint zapisywany przez LangGraph po każdej jednostce wykonania grafu — super-kroku, zdefiniowanym w następnej sekcji. Pamięć epizodyczna i semantyczna utrzymują się między wątkami. Pamięć dokumentowa przechowuje notatki projektowe, podsumowania badań i poznane konwencje w plikach, które mogą przeglądać ludzie i agenci. Pamięć proceduralna obejmuje instrukcje systemowe, definicje narzędzi i procedury wielokrotnego użytku, które można pobierać do zadania. Czasy życia w tabeli są przykładowe; retencja wynika z polityki aplikacji, a stan roboczy może trwać przez całe aktywne uruchomienie.
Na potrzeby implementacji pięć z sześciu typów sprowadza się do trzech warstw magazynowania. Pamięć krótkoterminowa staje się pamięcią hot — checkpointem bieżącego wątku. Pamięć epizodyczna i semantyczna stają się pamięcią cold — przypominaniem informacji między wątkami. Pamięć dokumentowa zachowuje zgromadzoną wiedzę projektową w formie czytelnej i bezpośrednio edytowalnej. Pamięć robocza jest grupowana z warstwą hot, ponieważ checkpointy mogą przechowywać stan potrzebny do odtworzenia aktywnego uruchomienia. Checkpoint nie jest pełnym wewnętrznym procesem obliczeniowym modelu. Procedury mogą być dostarczane wraz z agentem albo przechowywane i pobierane z plików lub innego magazynu. Warstwy te opisują wybory implementacyjne tego artykułu, a nie wzajemnie wykluczające się typy pamięci.
CoALA klasyfikuje pamięć roboczą, epizodyczną, semantyczną i proceduralną. Przegląd Memory in the Age of AI Agents organizuje natomiast pamięć według formy, funkcji i dynamiki, uwzględniając dokumenty, bazy kodu i przepływy pracy wielokrotnego użytku. Pliki mogą implementować kilka z tych kategorii. Ten artykuł wyróżnia pamięć dokumentową, aby uwidocznić jej odpowiedzialność za przechowywanie i utrzymanie danych.
Ten sam wzorzec magazynowania występuje w innych domenach. Agent Minecraft (Voyager) przechowuje wielokrotnego użytku umiejętności gry jako biblioteki kodu, a agenci webowi wyprowadzają wielokrotnego użytku przepływy przeglądania z udanych uruchomień. Do obu przypadków wrócę później. Pliki, które można przeglądać, i indeksowane wyszukiwanie mogą współistnieć: Voyager pobiera programy na podstawie embeddingów ich opisów.
Pamięć zarządzana przez agenta różni się również od stałego pipeline’u RAG tym, kto wykonuje zapis. Agent lub jego harness wybiera, co przechowywać, aktualizować i usuwać, a później decyduje, kiedy to pobrać.
Artykuł Generative Agents (Park i in., 2023) pokazał, jak daleko można posunąć ten model: symulowani agenci przechowywali, analizowali i pobierali własne wspomnienia. Jego strumień pamięci porządkował kandydatów według świeżości, ważności i trafności — to projekt, który nadal stanowi użyteczny punkt odniesienia dla wyszukiwania pamięci agenta.
Kompaktowanie utrzymuje użyteczność rozmowy
Większe context window nie eliminuje potrzeby wyboru tego, co ma przetrwać. Obecne API mogą podsumować starszą część rozmowy, zanim wypełni ona okno. Kompaktowanie po stronie serwera w Claude, nadal będące funkcją beta na dzień 6 września 2026 r., zwraca blok compaction, którego kolejne żądania używają zamiast wcześniejszej zawartości. Może to ograniczyć pracę związaną z podsumowywaniem po stronie klienta, ale podsumowanie może pominąć fakt potrzebny później.
Autorytatywny stan zadania przechowuj poza tym podsumowaniem: ukończone efekty, zgody, odwołania do źródeł i dokładne ograniczenia użytkownika. Checkpoint odtwarza wykonanie; kompaktowanie skraca kontekst modelu; pamięć długoterminowa wybiera wiedzę dla innej rozmowy. Testuj te trzy zachowania oddzielnie. Wymuś kompaktowanie w połowie testu i sprawdź, czy następna akcja nadal respektuje wcześniejsze ograniczenie. Nie używaj skompaktowanego transkryptu jako jedynego zapisu tego, co zostało zatwierdzone.
Pamięć krótkoterminowa agenta: magazyn checkpointów
LangGraph zapisuje checkpointy na granicach super-kroków — pojedynczego węzła lub partii węzłów uruchomionych równolegle. Przy domyślnym durability="async" następny krok może rozpocząć się, gdy zapis jest jeszcze wykonywany; durability="sync" czeka na utrwalenie danych przed kontynuacją, zwiększając opóźnienie zapisu. Odzyskiwanie po awarii korzysta z ostatniego utrwalonego checkpointu, niekoniecznie z ostatniego ukończonego kroku. To podstawa wstrzymywania i wznawiania, debugowania z cofaniem w czasie oraz przepływów HITL.
Checkpoint zawiera stan grafu potrzebny do wznowienia: AgentState z Części 1 — wiadomości, tożsamość, profil użytkownika, kroki planu, dane badawcze i tryb wykonania. Po przerwaniu HITL lub restarcie procesu LangGraph przywraca ostatni zapisany stan i korzysta z metadanych harmonogramowania, aby wybrać następny węzeł. Wznawia działanie na granicy ukończonego węzła, a nie w dowolnej linii kodu Python. Zapisane szczegóły obejmują ID checkpointu i znacznik czasu, wersję każdego kanału (tak LangGraph nazywa klucz stanu) oraz wersje kanałów, które każdy węzeł już widział. Numer kroku jest metadanymi tego checkpointu. Checkpoint różni się również od dołączanego tylko dziennika zdarzeń lub trace’a; Część 5 wyraźnie rozdziela te powierzchnie obserwowalności środowiska uruchomieniowego.
Jak działa checkpointing w LangGraph
BaseCheckpointSaver LangGraph to prosty interfejs: put() zapisuje checkpoint, get_tuple() odczytuje najnowszy checkpoint wątku, a list() zwraca historię. Każdy checkpoint jest identyfikowany przez (thread_id, checkpoint_ns, checkpoint_id), gdzie thread_id identyfikuje rozmowę, checkpoint_ns obsługuje przestrzeń nazw podgrafów, a checkpoint_id jest unikatową wersją.
Najważniejsza decyzja dotyczy backendu, który znajdzie się za tym interfejsem. PostgreSQL i Redis to dwa popularne wybory produkcyjne.
PostgreSQL a Redis
| Wymiar | PostgreSQL (langgraph-checkpoint-postgres) | Redis (langgraph-checkpoint-redis) |
|---|---|---|
| Model trwałości | Transakcje ACID, WAL i replikacja | Konfigurowalna trwałość: dziennik poleceń tylko do dołączania (AOF) lub okresowe snapshoty (RDB) |
| Historia checkpointów | Trwała historia na potrzeby wznowienia i debugowania | Retencja zależy od savera i ustawień eksmisji |
| Główne ograniczenie | Opóźnienie zapisu do bazy i wzrost tabel | Zużycie RAM, eksmisja i konfiguracja trwałości |
| Dopasowanie operacyjne | Zespoły, które już obsługują relacyjne bazy danych | Zespoły, które już obsługują Redis przy dużej przepustowości |
| Najlepszy wybór domyślny | Trwałe wznowienie i powtarzalne debugowanie | Stan sesji wrażliwy na opóźnienia, ale możliwy do odzyskania |
Ogólne benchmarki baz danych nie przewidują wydajności checkpointów. Zmierz rozmiar serializowanego stanu, częstotliwość zapisów, ustawienia trwałości i współbieżność własnego grafu.
PostgreSQL: trwały wybór domyślny
PostgreSQL jest bezpieczniejszym wyborem domyślnym dla większości zespołów. Checkpointy przetrwają awarie, otrzymujesz pełną semantykę transakcji, a historia checkpointów upraszcza debugowanie z cofaniem w czasie.
Uproszczona wersja konfiguracji checkpointów z memory/hot.py. Jeśli atakujący mógłby zapisywać checkpointy, ustaw LANGGRAPH_STRICT_MSGPACK=true lub skonfiguruj allowed_msgpack_modules. Ogranicza to deserializację do bezpiecznych lub jawnie zadeklarowanych typów; domyślna, liberalna konfiguracja ostrzega o niezarejestrowanych typach, ale nadal na nie pozwala.
import asyncio
from contextlib import asynccontextmanager
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
@asynccontextmanager
async def postgres_checkpointer(connection_string: str):
"""Yield a PostgreSQL-backed checkpoint store.
PostgreSQL gives us ACID guarantees — if a checkpoint write succeeds,
the state is durable even if the process crashes immediately after.
`from_conn_string` is itself an async context manager: it owns the
connection and closes it on exit, so the graph has to run inside it.
"""
async with AsyncPostgresSaver.from_conn_string(connection_string) as checkpointer:
# Create the checkpoint tables if they don't exist.
# This is idempotent — safe to call on every startup.
await checkpointer.setup()
yield checkpointer
async def main(authenticated_user_id: str) -> None:
# The graph lives inside the context manager's scope.
async with postgres_checkpointer(
"postgresql://user:pass@localhost:5432/agent_memory"
) as checkpointer:
graph = create_graph(checkpointer=checkpointer)
# Every invoke/stream call now persists state automatically.
config = {"configurable": {"thread_id": "user-123-session-1"}}
result = await graph.ainvoke(
{"user_id": authenticated_user_id,
"messages": [HumanMessage(content="Analyze NVDA")]}, config
)
# After the server authenticates the approver and validates approval
# of this exact draft, update the companion's approval field.
await graph.aupdate_state(config, {"report_approved": True})
# Continue the static interrupt_before pause; new input starts a new run.
result = await graph.ainvoke(None, config)
# Local fixture identity. A server supplies this only after authentication.
asyncio.run(main(authenticated_user_id="user-123"))
user_id w wejściu grafu pochodzi z uwierzytelnionego kontekstu serwera; thread_id jedynie lokalizuje checkpointy i nie ustanawia tożsamości ani nie autoryzuje dostępu do wątku. AsyncPostgresSaver korzysta z pakietu langgraph-checkpoint-postgres, który tworzy cztery tabele: checkpoints (serializowany stan), checkpoint_blobs (duże dane binarne), checkpoint_writes (oczekujące zapisy na potrzeby odzyskiwania po awarii) oraz checkpoint_migrations (wersja schematu). Współbieżni writerzy są rozdzielani przez klucz główny (thread_id, checkpoint_ns, checkpoint_id) i upserty, a nie przez blokady — dwóch workerów działających na tym samym wątku nie uszkodzi wzajemnie danych, ale też nie będzie się ze sobą koordynować.
Redis: gdy wąskim gardłem jest opóźnienie
Gdy opóźnienie checkpointów jest wąskim gardłem, Redis jest opcją dla stanu możliwego do odzyskania. Przed wyborem Redisa zamiast PostgreSQL zmierz rozmiar serializowanego stanu, ustawienia trwałości i współbieżność.
Uproszczona wersja konfiguracji checkpointów z memory/hot.py:
import asyncio
from contextlib import asynccontextmanager
from langgraph.checkpoint.redis.aio import AsyncRedisSaver
@asynccontextmanager
async def redis_checkpointer(redis_url: str):
"""Yield a Redis-backed checkpoint store.
Redis keeps checkpoints in memory for low-latency access.
Durability depends on RDB snapshots, AOF fsync policy, and replication.
AOF with appendfsync everysec can still lose about one second of writes
after a crash; enabling AOF alone is not a no-loss guarantee.
"""
async with AsyncRedisSaver.from_conn_string(redis_url) as checkpointer:
# Initialize Redis data structures
await checkpointer.asetup()
yield checkpointer
async def main() -> None:
# Same graph API, different backend.
async with redis_checkpointer("redis://localhost:6379") as checkpointer:
graph = create_graph(checkpointer=checkpointer)
asyncio.run(main())
AsyncRedisSaver z langgraph-checkpoint-redis przechowuje każdy checkpoint jako osobny dokument RedisJSON pod tym samym kluczem (thread_id, checkpoint_ns, checkpoint_id) co saver Postgresa. Przeprojektowanie w wersji v0.1.0 umieściło wartości checkpointów bezpośrednio w dokumencie i zastąpiło pobieranie poszczególnych kanałów ścieżką JSON.GET. Ta zmiana dotyczy pobierania wartości, a nie każdej operacji trwałego zapisu; pomiary opóźnień producenta zależą od obciążenia. Redis 8.0+ zawiera RedisJSON i RediSearch domyślnie — nie trzeba instalować dodatkowych modułów.
Wybierz politykę trwałości i fsync w Redisie zgodnie z akceptowanym oknem utraty danych. RDB może utracić zapisy od ostatniego snapshota; typowa polityka AOF appendfsync everysec może utracić około jednej sekundy danych. always zamienia opóźnienie zapisu na silniejszą trwałość, natomiast no pozostawia opróżnianie buforów systemowi operacyjnemu. Testuj odzyskiwanie przy rzeczywistych ustawieniach dysku i replikacji.
W przypadku wdrożeń z ograniczoną pamięcią ShallowRedisSaver przechowuje tylko najnowszy checkpoint każdego wątku — bez historii, ale przy minimalnym zużyciu RAM. Użyj tego rozwiązania, gdy potrzebujesz wstrzymywania i wznawiania, ale nie potrzebujesz debugowania z cofaniem w czasie.
Kiedy użyć którego rozwiązania
Użyj PostgreSQL, gdy:
- Potrzebujesz pełnej historii checkpointów do debugowania z cofaniem w czasie lub powtarzalnego wznawiania
- Trwałość danych jest niepodlegająca negocjacji (usługi finansowe, ochrona zdrowia)
- PostgreSQL już działa w Twoim stacku
- Agent wykonuje długie zadania, w których utrata stanu oznacza godziny ponownych obliczeń
- Chcesz mieć ujednolicony magazyn danych — PostgreSQL z pgvector może być jednym backendem dla checkpointów, pamięci długoterminowej i wyszukiwania wektorowego
Użyj Redis, gdy:
- Opóźnienie checkpointów jest Twoim wąskim gardłem (czat w czasie rzeczywistym, streamingowy UX)
- Budujesz boty głosowe lub doświadczenia streamingowe, w których dostęp do checkpointów znajduje się na ścieżce krytycznej pod względem opóźnień
- Potrzebujesz skalowania horyzontalnego między wieloma niezależnymi wątkami. Jeśli kilku agentów modyfikuje współdzielony stan, przypisz właściciela temu stanowi i koordynuj działania poza saverem checkpointów.
- Obsługujesz krótkotrwałe sesje, w których utrata checkpointu jest możliwa do naprawienia
- Chcesz używać cache’owania semantycznego do ograniczenia zbędnych wywołań LLM (Redis LangCache przechowuje semantycznie podobne zapytania w cache, aby unikać powtarzających się wywołań LLM)
Inne opcje: langgraph-checkpoint-sqlite działa w lokalnym środowisku programistycznym i wdrożeniach jednoprokesowych. W stackach natywnych dla AWS langgraph-checkpoint-aws udostępnia DynamoDBSaver z automatycznym przenoszeniem payloadów — udokumentowany saver przenosi dane powyżej progu 350 KB, gdy skonfigurowany jest bucket S3. Ten próg wynika z polityki implementacji, a nie z limitu 400 KB elementu DynamoDB. Ceny serverless i brak infrastruktury do zarządzania sprawiają, że rozwiązanie to jest atrakcyjne przy zmiennym obciążeniu.
Pamięć długoterminowa: pamiętanie między sesjami
Pamięć hot obsługuje bieżącą rozmowę. Pamięć długoterminowa dotyczy użytkownika, który wraca w następnym tygodniu: przechowuje fakty, preferencje i historię interakcji utrzymujące się między wątkami.
LangGraph udostępnia interfejs Store do pamięci między wątkami za pośrednictwem klasy BaseStore. Każdy element pamięci jest parą (namespace, key) zawierającą wartość JSON i opcjonalny embedding wektorowy. Przestrzeń nazw zazwyczaj koduje użytkownika lub organizację: ("user", "user-123", "preferences").
Magazyn wektorowy: semantyczne przypominanie z Qdrant
Gdy agent musi przypomnieć sobie nieustrukturyzowane fakty („Co użytkownik powiedział o swoim horyzoncie inwestycyjnym?”), wyszukiwanie wektorowe zapewnia semantyczne przypominanie. Zamiast dokładnych wyszukiwań po kluczu agent odpytuje magazyn według znaczenia.
Qdrant to wyspecjalizowana baza wektorowa napisana w Rust, obsługująca przechowywanie embeddingów, indeksowanie (Hierarchical Navigable Small World, czyli HNSW) i wyszukiwanie z filtrami. Szczegółowo omawiałem HNSW i jego kompromisy w moim wpisie o rankingu wyszukiwania. Qdrant oferuje również serwer MCP, który działa jako semantyczna warstwa pamięci — przydatna, jeśli framework agenta obsługuje Model Context Protocol.
Poniższy przykład to niezależny, ilustracyjny projekt z Qdrant. Nie jest uproszczoną wersją bieżącego memory/long.py. Obecny projekt przechowuje profile użytkowników z dokładnym filtrowaniem po user_id i placeholderem w postaci wektora zerowego. Rzeczywista integracja embeddingów pozostaje zadaniem na przyszłość. Handler żądania musi uwierzytelnić żądanie i utworzyć principal na podstawie zweryfikowanej tożsamości; klient nigdy jej nie dostarcza. Filtr Qdrant określa zakres pobierania, a nie autoryzację.
from qdrant_client import QdrantClient
from qdrant_client.models import (
PointStruct, Distance, VectorParams, Filter, FieldCondition, MatchValue,
)
import hashlib
import json
from dataclasses import dataclass
@dataclass(frozen=True)
class AuthenticatedPrincipal:
"""Created by the server after authentication, never from request JSON."""
user_id: str
class UserMemoryStore:
"""Long-term memory backed by Qdrant vector search.
Stores user facts as embedded vectors for semantic retrieval.
Each fact is a short natural-language statement about the user.
"""
def __init__(self, qdrant_url: str, collection_name: str = "user_memory"):
self.client = QdrantClient(url=qdrant_url)
self.collection_name = collection_name
self._ensure_collection()
def _ensure_collection(self):
"""Create the collection if it doesn't exist."""
collections = [c.name for c in self.client.get_collections().collections]
if self.collection_name not in collections:
self.client.create_collection(
collection_name=self.collection_name,
vectors_config=VectorParams(
size=1536, # text-embedding-3-small dimensions
distance=Distance.COSINE,
),
)
def store_fact(
self, principal: AuthenticatedPrincipal, fact: str, embedding: list[float]
):
"""Store a user fact with its embedding."""
identity = json.dumps([principal.user_id, fact], ensure_ascii=False).encode()
point_id = hashlib.sha256(identity).hexdigest()[:32]
self.client.upsert(
collection_name=self.collection_name,
points=[PointStruct(
id=point_id,
vector=embedding,
payload={"user_id": principal.user_id, "fact": fact},
)],
)
def recall(
self,
principal: AuthenticatedPrincipal,
query_embedding: list[float],
top_k: int = 5,
):
"""Retrieve the most relevant facts for a user given a query."""
results = self.client.query_points(
collection_name=self.collection_name,
query=query_embedding,
query_filter=Filter(
must=[FieldCondition(
key="user_id", match=MatchValue(value=principal.user_id)
)]
),
limit=top_k,
)
return [hit.payload["fact"] for hit in results.points]
ID punktu jest skrótem tablicy JSON zawierającej ID użytkownika i fakt, dzięki czemu separatory wewnątrz którejkolwiek wartości nie mogą połączyć dwóch tożsamości. Na przykład użytkownik a:b z faktem c musi różnić się od użytkownika a z faktem b:c. 32 znaki szesnastkowe pasują do reprezentacji ID punktu UUID w Qdrant.
Przepływ składa się z trzech kroków. W tym ilustracyjnym projekcie LLM wyodrębnia kluczowe fakty z interakcji („użytkownik ma wysoką tolerancję ryzyka”, „użytkownik interesuje się akcjami spółek półprzewodnikowych”). Fakty te są osadzane i przechowywane w Qdrant. Na początku kolejnej rozmowy serwer przekazuje uwierzytelnionego principal, a agent odpytuje Qdrant nową wiadomością użytkownika, aby przypomnieć sobie istotny kontekst. Obecny Market Analyst Agent nie implementuje jeszcze tego przepływu ekstrakcji semantycznej i embeddingów.
Scoring pobierania: więcej niż podobieństwo cosinusowe
Surowe podobieństwo cosinusowe to punkt wyjścia, ale produkcyjne systemy pamięci wymagają bogatszego pobierania. Artykuł Generative Agents (Park i in., 2023) wprowadził funkcję scoringową łączącą trzy sygnały:
- Świeżość: oparte na regułach wygaszanie, dzięki któremu nowsze wspomnienia otrzymują wyższe wyniki. Wykładnicza funkcja zaniku sprawia, że fakt z wczoraj wyprzedza równoważny fakt sprzed sześciu miesięcy.
- Ważność: znaczenie oceniane przez LLM w skali 1–10. „Portfel użytkownika stracił 40%” otrzymuje wyższy wynik niż „użytkownik powiedział cześć”.
- Trafność: podobieństwo cosinusowe embeddingu zapytania i zapisanego faktu.
Artykuł normalizuje wszystkie trzy sygnały do porównywalnych skal przed ich połączeniem. Zrób to samo przed dostrojeniem wag; w przeciwnym razie surowa ważność 1–10 zdominuje sygnał 0–1. Końcowy wynik pobierania jest sumą ważoną: score = alpha * recency + beta * importance + gamma * relevance. Dzięki temu świeże i ważne fakty nie zostaną zakopane pod starymi, ale semantycznie podobnymi. Dla prototypu analizy finansowej zacząłbym od alpha = 0.3 dla świeżości, beta = 0.2 dla ważności i gamma = 0.5 dla trafności, ponieważ to bieżące zapytanie zwykle decyduje, który z poprawnych skądinąd faktów powinien trafić do kontekstu. Artykuł Generative Agents używał równych wag; podane wartości są proponowanym punktem wyjścia, a nie zmierzoną poprawą. Dostrój je na podstawie held-out recall i kontroli jakości zadania, zanim zaczniesz na nich polegać.
Alternatywy dla wyszukiwania wektorowego
Wyszukiwanie wektorowe jest potężne, ale nie zawsze jest właściwym narzędziem. Oto kiedy warto użyć alternatyw:
| Podejście | Najlepsze zastosowanie | Główny koszt operacyjny |
|---|---|---|
| Wyszukiwanie wektorowe (Qdrant) | Semantyczne przypominanie nieustrukturyzowanych faktów | Cykl życia embeddingów i indeksu |
| Magazyn klucz-wartość (Redis) | Ustrukturyzowane profile i preferencje użytkowników | Zużycie pamięci i polityka trwałości |
| Magazyn dokumentów (pliki) | Wiedza projektowa i notatki zarządzane przez agenta | Współbieżność, uprawnienia i wyszukiwanie |
| Wyszukiwanie pełnotekstowe (PostgreSQL indeks GIN) | Wyszukiwanie słów kluczowych w historii rozmów | Wzrost indeksu i strojenie zapytań |
| Graf wiedzy (Neo4j) | Relacje encji i zapytania wieloskokowe | Modelowanie grafu i kolejny system danych |
| Hybrydowe (wektorowe + słowa kluczowe) | Przypominanie przy zmiennym znaczeniu zapytania | Dwie ścieżki scoringu do strojenia i ewaluacji |
Magazyny klucz-wartość dobrze sprawdzają się dla danych ustrukturyzowanych. Jeśli pamięć długoterminowa to profil użytkownika — tolerancja ryzyka, horyzont inwestycyjny, preferowane sektory — hash Redis lub kolumna JSONB w PostgreSQL są prostsze i szybsze niż osadzanie oraz odpytywanie wektorów. Wyszukiwania wektorowego używaj, gdy pamięć jest nieustrukturyzowana, a zapytania pobierające różnią się sposobem sformułowania.
Wbudowany Store LangGraph udostępnia interfejs klucz-wartość oparty na przestrzeni nazw, z opcjonalnym wyszukiwaniem wektorowym. API BaseStore jest proste: put(), get(), search() i delete() z hierarchicznym zakresem przestrzeni nazw. Dostępne są trzy implementacje:
InMemoryStore— do programowania i testów (dane są tracone po zakończeniu procesu)PostgresStore— produkcyjny, trwały magazyn z pełnym odpytywaniem SQLAsyncRedisStore— pamięć między wątkami z wyszukiwaniem wektorowym, obsługą TTL i filtrowaniem metadanych
Konfiguracja index włącza wyszukiwanie wektorowe zapisanych elementów z użyciem konfigurowalnego modelu embeddingowego. W wielu przypadkach ten wbudowany Store wystarczy bez sięgania po dedykowaną bazę wektorową.
import asyncio
from langgraph.store.memory import InMemoryStore
# Create a store with vector search enabled
store = InMemoryStore(
index={
"dims": 1536,
"embed": my_embedding_function, # e.g., OpenAI text-embedding-3-small
}
)
async def main() -> None:
# Store a user preference (namespace scopes to user).
await store.aput(
namespace=("user", "user-123", "preferences"),
key="risk-profile",
value={"risk_tolerance": "high", "horizon": "long-term"},
)
# Semantic search across the user's memories.
# The namespace prefix is positional here — `search`/`asearch` declare it
# as positional-only `namespace_prefix`, unlike `aput`.
results = await store.asearch(
("user", "user-123"),
query="What is their investment style?",
limit=5,
)
asyncio.run(main())
Wybór strategii pamięci długoterminowej
Zacznij od klucz-wartość, jeśli pamięć jest ustrukturyzowana i dobrze zdefiniowana (profile użytkowników, ustawienia, nazwane encje). Dodaj wyszukiwanie wektorowe, gdy potrzebujesz semantycznego pobierania nieustrukturyzowanych faktów lub gdy sposób formułowania zapytań jest nieprzewidywalnie zmienny.
Grafy wiedzy są uzasadnione, gdy znaczenie mają relacje między encjami, np. „O które firmy pytał użytkownik, które są konkurentami NVDA?”. Najciekawszym nowszym projektem jest Graphiti (firmy Zep), który buduje graf wiedzy uwzględniający czas, śledzący kiedy fakty były prawdziwe, a nie tylko co było prawdziwe. Jego relacje czasowe mogą zachowywać przedziały ważności i wartości zastąpione nowszymi; logika ekstrakcji i aktualizacji nadal decyduje, czy fakt jest aktualny. Artykuł Zep raportuje dokładność DMR na poziomie 94,8% dla ocenianego systemu Zep opartego na Graphiti i GPT-4 Turbo, w porównaniu z 94,4% dla pełnego kontekstu. DMR wykorzystuje rozmowy liczące 60 wiadomości i ograniczone zadanie pobierania faktów. Tak niewielka różnica nie dowodzi ogólnej przewagi grafów czasowych.
Problemem jest operacyjność. Uruchamianie bazy grafowej nie jest trywialne, a w większości aplikacji agentowych wyszukiwanie wektorowe z filtrowaniem metadanych zapewnia podobne możliwości przy mniejszej infrastrukturze.
Zarządzane frameworki pamięci, takie jak Mem0 i Letta (wcześniej MemGPT), obsługują za Ciebie pipeline ekstrakcji, konsolidacji i pobierania. Podejście Mem0 jest warte uwagi: LLM wyodrębnia potencjalne wspomnienia, silnik decyzyjny porównuje każdy nowy fakt z istniejącymi wpisami w magazynie wektorowym, a resolver decyduje, czy dodać, zaktualizować, usunąć wpis czy nie robić nic. Dzięki temu magazyn pamięci pozostaje spójny i pozbawiony redundancji. Letta przyjmuje perspektywę systemu operacyjnego: agenci zarządzają własnym context window za pomocą narzędzi do zarządzania pamięcią, autonomicznie przenosząc dane między „pamięcią podstawową” (w kontekście) i „pamięcią archiwalną” (poza kontekstem). Oba rozwiązania warto ocenić, jeśli zależy Ci na szybszym wejściu na produkcję i nie potrzebujesz pełnej kontroli nad pipeline’em pamięci.
Pamięć dokumentowa: szafka z dokumentami agenta
Magazyny wektorowe i backendy klucz-wartość dobrze obsługują semantyczne przypominanie i ustrukturyzowane wyszukiwanie. Zgromadzony kontekst projektu — konwencje, notatki badawcze i decyzje przenoszone między sesjami — często powinien znajdować się w plikach, które ludzie mogą czytać, przeglądać i wersjonować.
To jest pamięć dokumentowa: agent odczytuje i zapisuje ustrukturyzowane pliki (Markdown, JSON, YAML) w znanym katalogu. Bez embeddingów, bez bazy danych i bez infrastruktury. Po prostu pliki na dysku, które zarówno agent, jak i deweloper mogą cat, grep, git diff oraz ręcznie edytować.
W jednej ewaluacji przeprowadzonej przez dostawcę Letta zaraportowała 74,0% dokładności na LoCoMo — benchmarku pytań i odpowiedzi dla długich rozmów — dla agenta GPT-4o mini korzystającego z dołączonych plików, automatycznych embeddingów, semantycznego search_files oraz obowiązkowych reguł używania narzędzi wyszukiwania. Najlepszy wariant grafowy Mem0 uzyskał 68,5%. To wynik jednego dostawcy, modelu, benchmarku i harnessu. Pokazuje, że interfejs oparty na plikach może dobrze działać w tej konfiguracji; nie dowodzi, że surowy Markdown lub wyszukiwanie słów kluczowych wystarczą. Korzyść operacyjna jest odrębna: deweloperzy mogą bezpośrednio czytać, edytować i porównywać diffem przechowywaną wiedzę.
Większe context window sprawiają również, że odczytywanie całych plików jest praktyczne w przypadku niektórych dokumentów projektowych. Chunked retrieval nadal pasuje do dużych korpusów, ale krótki plik z konwencjami lub przekazaniem pracy często można załadować bezpośrednio. Wybór zależy od rozmiaru dokumentu, precyzji pobierania, budżetu kontekstu oraz częstotliwości, z jaką ludzie muszą przeglądać lub edytować pamięć.
Dlaczego pliki?
W przypadku długotrwałego projektu agenta używaj katalogu dobrze uporządkowanych notatek, gdy ludzie potrzebują możliwego do przeglądania zapisu. Rozważ agenta kodującego, który pracuje nad jednym projektem przez kilka tygodni:
- Uczy się, że projekt używa Pydantic v2, a nie v1
- Odkrywa, że testy muszą być uruchamiane za pomocą
pytest -x --tb=short - Gromadzi wiedzę o architekturze bazy kodu
- Poznaje preferencje dewelopera („zawsze używaj
pathlib, nigdyos.path”)
Fakty te mogłyby znajdować się w systemie wektorowym lub klucz-wartość. W tym przypadku lepszym wyborem domyślnym są pliki, ponieważ deweloper musi czytać, edytować, przeglądać i wersjonować powiązane notatki. Wyszukiwanie słów kluczowych lub semantyczne dodaj dopiero wtedy, gdy wymaga tego korpus dokumentów i wzorzec zapytań. Jeśli agent nauczy się czegoś błędnego, otwórz plik i popraw tę informację.
Claude Code, Cursor i Devin Desktop używają wariantów tego wzorca. Poniższe przykłady pokazują, jak każdy z nich przechowuje i ładuje swoje pliki.
Implementacja magazynu pamięci plikowej
Implementacja jest celowo prosta. Agent otrzymuje cztery operacje: zapis dokumentu, odczyt dokumentu, wyświetlenie dostępnych dokumentów oraz wyszukiwanie słów kluczowych we wszystkich dokumentach.
Poniżej znajduje się niezależny, ilustracyjny magazyn plików z surowym Markdownem. Nie jest on uproszczoną wersją obecnego memory/document.py. Obecny projekt używa DocumentMemory, które wymaga przestrzeni nazw i klucza oraz zapisuje kopertę JSON zawierającą content, metadata i created_at. Ten szkic definiuje inny projekt, aby pokazać kompromisy związane z czytelnymi dla człowieka plikami Markdown:
from pathlib import Path
import json
class FileMemory:
"""Document memory backed by the local filesystem.
Stores agent knowledge as human-readable files organized by topic.
No embeddings, no database — just files that both the agent and
the developer can read, edit, and version-control.
"""
def __init__(self, base_dir: str | Path):
self.base_dir = Path(base_dir).resolve()
self.base_dir.mkdir(parents=True, exist_ok=True)
def _resolve_path(self, path: str) -> Path:
"""Return a path inside base_dir, rejecting escapes and symlinks."""
requested = Path(path)
if requested.is_absolute() or ".." in requested.parts:
raise ValueError("path must be relative to base_dir without traversal")
resolved = (self.base_dir / requested).resolve()
try:
resolved.relative_to(self.base_dir)
except ValueError as error:
raise ValueError("path must stay inside base_dir") from error
return resolved
def write_doc(self, path: str, content: str, metadata: dict | None = None):
"""Write or overwrite a document at the given path.
Paths are relative to base_dir. Directories are created automatically.
Metadata (if provided) is stored as a JSON sidecar file.
"""
full_path = self._resolve_path(path)
full_path.parent.mkdir(parents=True, exist_ok=True)
full_path.write_text(content, encoding="utf-8")
if metadata:
meta_path = self._resolve_path(
str(full_path.relative_to(self.base_dir).with_suffix(full_path.suffix + ".meta"))
)
meta_path.write_text(json.dumps(metadata, indent=2), encoding="utf-8")
def read_doc(self, path: str) -> str | None:
"""Read a document by path. Returns None if not found."""
full_path = self._resolve_path(path)
if full_path.exists():
return full_path.read_text(encoding="utf-8")
return None
def list_docs(self, pattern: str = "**/*") -> list[str]:
"""List documents matching a glob pattern."""
self._resolve_path(pattern)
return [
str(self._resolve_path(str(p.relative_to(self.base_dir))).relative_to(self.base_dir))
for p in self.base_dir.glob(pattern)
if self._resolve_path(str(p.relative_to(self.base_dir))).is_file()
and not p.name.endswith(".meta")
]
def search_docs(self, query: str, pattern: str = "**/*.md") -> list[dict]:
"""Search documents by keyword. Returns matching files with context.
This is intentionally simple — grep-style keyword search.
For semantic search, use a vector store instead.
# ponytail: linear scan of file bytes; add an index when measured
# latency, concurrency, or retrieval quality requires it.
"""
self._resolve_path(pattern)
results = []
for path in self.base_dir.glob(pattern):
path = self._resolve_path(str(path.relative_to(self.base_dir)))
if not path.is_file() or path.name.endswith(".meta"):
continue
content = path.read_text(encoding="utf-8")
if query.lower() in content.lower():
# Return the paragraph containing the match for context
for paragraph in content.split("\n\n"):
if query.lower() in paragraph.lower():
results.append({
"path": str(path.relative_to(self.base_dir)),
"match": paragraph.strip()[:500],
})
return results
Helper ścieżki jest celowo współdzielony przez odczyty, zapisy i wyniki glob: ścieżki względne nadal mogą wyprowadzić poza katalog przez .. lub istniejący symlink. Ta ilustracyjna klasa jest przeznaczona dla zaufanego systemu jednoużytkownikowego lub kontrolowanego systemu plików. Sprawdza ścieżkę po rozwiązaniu przed użyciem; na wrogiej granicy wielu tenantów używaj operacji względnych względem deskryptora z no-follow, aby mutacja systemu plików nie mogła wyścignąć tego sprawdzenia. Po skopiowaniu klasy uruchom tę małą kontrolę regresji:
from tempfile import TemporaryDirectory
with TemporaryDirectory() as root:
memory = FileMemory(root)
memory.write_doc("notes/ok.md", "safe memory")
assert memory.read_doc("notes/ok.md") == "safe memory"
assert memory.list_docs() == ["notes/ok.md"]
assert memory.search_docs("safe")[0]["path"] == "notes/ok.md"
(Path(root) / "escape").symlink_to(Path(root).parent, target_is_directory=True)
for operation in (
lambda: memory.write_doc("../escape.md", "nope"),
lambda: memory.read_doc("/tmp/escape.md"),
lambda: memory.read_doc("escape/outside.md"),
lambda: memory.list_docs("../**/*"),
lambda: memory.search_docs("safe", "../**/*.md"),
):
try:
operation()
except ValueError:
pass
else:
raise AssertionError("FileMemory accepted an escaped path")
Struktura katalogów
Większość wartości pamięci dokumentowej wynika ze sposobu ułożenia katalogu. Oto struktura, której użyłbym dla agenta badawczego. Market Analyst Agent używa przestrzeni nazw pod memory/documents/, ale jego obecne DocumentMemory zapisuje każdy wpis jako kopertę JSON z łańcuchem content, a nie jako surowy Markdown. Układ surowego Markdownu poniżej należy do niezależnego, ilustracyjnego projektu FileMemory przedstawionego wyżej:
.agent-memory/
README.md # What this directory is, for human readers
PROGRESS.md # Handoff for the next session: what is done, what is next
user-profiles/
user-123.md # Preferences, history, risk profile
user-456.md
research/
NVDA-2026-02.md # Research notes from recent analysis
TSLA-2026-01.md
conventions/
analysis-format.md # How to structure analysis reports
data-sources.md # Preferred data sources and API patterns
learnings/
common-errors.md # Mistakes the agent has learned to avoid
tool-patterns.md # Effective tool call sequences
W ilustracyjnym projekcie FileMemory każdy dokument jest zapisany w Markdownie, a jego przeznaczenie wynika wprost ze ścieżki. Możesz git diff cały katalog pamięci, aby zobaczyć, czego agent nauczył się podczas sesji, git revert błędną informację lub skopiować katalog do innego projektu. Koperty JSON obecnego projektu zachowują strukturę przestrzeni nazw i klucza, ale nie zapewniają takiego samego doświadczenia diffowania surowego Markdownu.
Kiedy użyć pamięci dokumentowej, wektorowej lub klucz-wartość
Trzy backendy pamięci obsługują różne wzorce dostępu:
| Wymiar | Magazyn wektorowy | Magazyn klucz-wartość | Magazyn dokumentów |
|---|---|---|---|
| Wzorzec zapytania | „Znajdź fakty podobne do X” | „Pobierz wartość dla klucza” | „Odczytaj dokument ze ścieżki” |
| Najlepsze zastosowanie | Nieustrukturyzowane, zmienne pobieranie | Ustrukturyzowane wyszukiwanie | Kontekst projektu, notatki |
| Czytelność dla człowieka | Czytelne payloady tekstowe | Częściowa (JSON) | Tak (Markdown) |
| Możliwość debugowania | Inspekcja payloadów i wyników | Łatwa (dokładne klucze) | Inspekcja plików i wyszukiwanie |
| Możliwość wersjonowania | Przez eksporty lub dzienniki zmian | Możliwa | Tak (natywnie z git) |
| Infrastruktura embeddingów | Wymagana | Niepotrzebna | Niepotrzebna |
| Skala | Miliony faktów | Miliony kluczy | Zależna od liczby bajtów i indeksu |
| Możliwości wyszukiwania | Podobieństwo semantyczne | Dokładne dopasowanie | Ścieżka, słowo kluczowe, opcjonalny indeks |
Używaj pamięci dokumentowej, gdy:
- Agent gromadzi wiedzę projektową podczas wielu sesji
- Deweloperzy muszą przeglądać, edytować lub nadpisywać to, co agent „wie”
- Wiedza ma postać dokumentów (notatek, podsumowań, konwencji), a nie odizolowanych faktów
- Chcesz wersjonować pamięć agenta za pomocą git
- Wymaganiem bezwzględnym jest brak infrastruktury
Używaj magazynów wektorowych, gdy:
- Potrzebujesz rozmytego wyszukiwania semantycznego („znajdź wspomnienia związane z X”)
- Sposób formułowania zapytań jest nieprzewidywalnie zmienny
- Masz od tysięcy do milionów pojedynczych faktów
Używaj magazynów klucz-wartość, gdy:
- Potrzebujesz dokładnych i szybkich wyszukiwań danych ustrukturyzowanych (profile użytkowników, ustawienia)
- Schemat danych jest dobrze zdefiniowany
Trzy magazyny mogą współistnieć, ale nie jest to wymagane. Obecny Market Analyst Agent używa checkpointów PostgreSQL do pamięci hot, Qdrant do dokładnego przechowywania profili użytkowników z placeholderami wektorów oraz namespacowanego magazynu dokumentów z kopertami JSON. Warianty semantycznego przypominania i surowego Markdownu w tym artykule są ilustracyjnymi rozszerzeniami.
Przykłady z rzeczywistych systemów
Ten wzorzec jest już szeroko stosowany w asystentach AI do kodowania:
- Claude Code odczytuje pliki
CLAUDE.mdz katalogu głównego projektu i katalogów nadrzędnych oraz utrzymuje plik pamięci dla projektu w~/.claude/projects/na potrzeby wiedzy między sesjami. System pamięci składa się ze zwykłych plików Markdown, a pliki na poziomie projektu można commitować razem z kodem. - Cursor ładuje reguły projektu z
.cursor/rulesjako pliki.mdc— konwencje kodowania, preferencje frameworka i decyzje architektoniczne — z frontmatterem sterującym tym, kiedy każda reguła ma zastosowanie. - Starszy agent Cascade w Devin Desktop odczytuje reguły z
.devin/rules/, przy czym.windsurf/rules/i znajdujący się w katalogu głównym.windsurfrulespozostają starszymi mechanizmami awaryjnymi. Cascade przechowuje automatycznie generowaną pamięć lokalnie dla każdego workspace’u i pobiera ją później; domyślny agent Devin Local dla nowych kart nie zapisuje pamięci. - Narzędzie pamięci Anthropic dla Claude API jest narzędziem po stronie klienta, którym model steruje za pomocą operacji na plikach —
view,create,str_replace,insert,deleteirename— w katalogu/memories. To aplikacja implementuje każde polecenie, więc decyduje, gdzie faktycznie znajdują się pliki (dysk lokalny, S3, baza danych).
Warianty oparte na plikach przechowują wiedzę agenta jako czytelny dla człowieka tekst z jawnymi operacjami odczytu i zapisu, a żaden z nich nie wymaga pipeline’u embeddingów. Agent decyduje, co zapisać; gdy tekst znajduje się w lokalnym katalogu zarządzanym przez Git, deweloper może zobaczyć i edytować go w git diff. Gdy handler narzędzia pamięci Anthropic mapuje /memories na S3 lub bazę danych, możliwości inspekcji i wersjonowania zależą od tej implementacji.
Deklaratywne notatki i wykonywalne umiejętności
Wiedza oparta na plikach występuje również poza asystentami programistycznymi, ale format magazynowania nie mówi, jak jest używana. Voyager przechowuje programy JavaScript wielokrotnego użytku, które agent może wykonywać. Główna metoda Agent Workflow Memory dodaje natomiast wyprowadzone przepływy webowe do kontekstu promptu jako wskazówki dla kolejnych działań. Osobny eksperyment AWM_AS udostępnia przepływy jako wywoływalne akcje. Procedura opisana w kontekście i procedura wykonywalna wymagają różnych kontroli.
Testuj wywoływalne umiejętności, uruchamiając je w kontrolowanym środowisku i sprawdzając efekty. Przeglądaj notatki projektowe i przepływy kontekstowe pod kątem dostarczanych faktów, ograniczeń i wskazówek dotyczących działań, a następnie sprawdź, czy instrukcje poprawiają zachowanie downstream. Każda z tych form może doprowadzić do szkodliwej akcji; żadna nie nadaje dodatkowych uprawnień.
Ta sama granica oddziela pamięć od umiejętności i narzędzi. Standard Agent Skills używa plików SKILL.md, aby powiedzieć agentowi, jak wykonywać określoną klasę pracy; pamięć rejestruje fakty poznane podczas pracy nad projektem lub wcześniejszego uruchomienia. Część 3 wyznacza sąsiednią granicę między umiejętnością a narzędziem. Wybierz magazyn plikowy dla możliwego do inspekcji kontekstu poznanego przez agenta; umiejętność lub narzędzie wybieraj tylko wtedy, gdy wymaganiem jest procedura wielokrotnego użytku lub określona zdolność.
Skalowanie pamięci dokumentowej do produkcji
Opisana wyżej implementacja oparta na plikach pasuje do kontrolowanego systemu plików jednoużytkownikowego. Wielu tenantów i współbieżni writerzy wymagają jawnej kontroli dostępu oraz koordynacji zapisów, niezależnie od liczby dokumentów.
Surowy magazyn nie ma koordynacji współbieżnych zapisów, modelu tenantów ani indeksu wyszukiwania. Zanim go zastąpisz, zmierz te wymagania. Baza danych lub magazyn obiektowy może zapewnić inne kontrakty współbieżności i dostępu; pliki również można indeksować.
Trzy popularne podejścia:
Podejście A: hybryda z cienką warstwą bazy danych
Pozostaw pliki jako format authoringu (deweloperzy edytują Markdown lokalnie), ale w runtime serwuj dane z bazy. Podczas wdrożenia synchronizuj pliki z wierszami PostgreSQL. Agent odczytuje dane z bazy, a nie z dysku. Zapewnia to:
- Wygodę dla deweloperów (edycja Markdownu i commitowanie do gita)
- Wydajność zapytań produkcyjnych (indeksowane odczyty z bazy)
- Czyste rozdzielenie authoringu i serwowania
Podejście B: magazyn obiektowy + sidecar z indeksem wektorowym
Przechowuj dokumenty w S3/GCS jako obiekty, a kolekcja Qdrant niech indeksuje ich embeddingi. Agent odpytuje Qdrant o odpowiednie ID dokumentów, a następnie pobiera treść z magazynu obiektowego. Rozwiązanie skaluje się horyzontalnie i obsługuje wyszukiwanie semantyczne, ale dodaje złożoność: trzeba zarządzać dwoma systemami, utrzymywać pipeline embeddingów i obsługiwać eventual consistency między magazynem a indeksem.
Podejście C: ustrukturyzowany magazyn dokumentów z PostgreSQL (rekomendowane)
Przechowuj dokumenty jako wiersze JSONB w PostgreSQL, z wyszukiwaniem pełnotekstowym (indeks GIN) i opcjonalnymi embeddingami wektorowymi (pgvector). Zapewnia to wyszukiwanie hybrydowe (słowa kluczowe + semantyka), transakcje ACID i jeden system operacyjny.
Szkic podejścia C. Połączony wynik wykonuje dokładny scoring na ograniczonym korpusie tenanta; nie używa indeksu approximate nearest-neighbor (ANN). pgvector wymaga bezpośredniego sortowania rosnącego po odległości z LIMIT-em dla tej ścieżki indeksowania. W przypadku większego korpusu pobierz oddzielnie ograniczone zbiory kandydatów słów kluczowych i wektorów, a następnie połącz ich rankingi. To wzorzec RLS, a nie gotowy do bezpośredniego użycia kod aplikacji: jego rola bazy danych musi być dostępna wyłącznie dla zaufanego serwera aplikacji. Serwer uwierzytelnia żądanie i tworzy principal; nie przyjmuje ID tenanta od wywołującego. PostgreSQL RLS sprawia następnie, że zakres ten jest egzekwowalny nawet wtedy, gdy zapytanie pominie później predykat tenanta.
from typing import Optional
from dataclasses import dataclass
import asyncpg
@dataclass(frozen=True)
class AuthenticatedPrincipal:
"""The verified identity returned by the application's authentication layer."""
tenant_id: str
class ProductionDocumentMemory:
"""Illustrative PostgreSQL document memory with hybrid search and RLS.
Apply this schema and policy as the table owner during deployment:
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
tenant_id TEXT NOT NULL,
path TEXT NOT NULL,
content TEXT NOT NULL,
metadata JSONB,
embedding vector(1536), -- pgvector extension
ts_vector tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, path)
);
CREATE INDEX ON documents USING GIN(ts_vector);
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_documents ON documents
USING (tenant_id = current_setting('app.tenant_id', true))
WITH CHECK (tenant_id = current_setting('app.tenant_id', true));
`FORCE` also subjects the table owner to the policy. Superusers and roles with
`BYPASSRLS` still bypass it, so neither belongs in the application's pool.
"""
def __init__(self, pool: asyncpg.Pool):
self.pool = pool
async def write(
self,
principal: AuthenticatedPrincipal,
path: str,
content: str,
metadata: Optional[dict] = None,
embedding: Optional[list[float]] = None,
):
"""Write or update a document.
Sketch: on a real pool you must register codecs first, or asyncpg
raises DataError — `set_type_codec` for the JSONB metadata column
and pgvector's `register_vector` for the embedding.
"""
async with self.pool.acquire() as conn:
async with conn.transaction():
# true keeps this trusted context to this transaction only.
await conn.execute(
"SELECT set_config('app.tenant_id', $1, true)", principal.tenant_id
)
await conn.execute(
"""
INSERT INTO documents (tenant_id, path, content, metadata, embedding)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (tenant_id, path) DO UPDATE
SET content = EXCLUDED.content,
metadata = EXCLUDED.metadata,
embedding = EXCLUDED.embedding
""",
principal.tenant_id, path, content, metadata, embedding,
)
async def search(
self,
principal: AuthenticatedPrincipal,
query: str,
embedding: Optional[list[float]] = None,
limit: int = 5,
) -> list[dict]:
"""Hybrid search: full-text + optional vector similarity."""
async with self.pool.acquire() as conn:
async with conn.transaction():
await conn.execute(
"SELECT set_config('app.tenant_id', $1, true)", principal.tenant_id
)
if embedding:
# Hybrid scoring: 0.6 * text relevance + 0.4 * vector similarity
rows = await conn.fetch(
"""
SELECT path, content, metadata,
(0.6 * ts_rank(ts_vector, plainto_tsquery('english', $1)) +
0.4 * COALESCE(1 - (embedding <=> $2), 0)) AS score
FROM documents
WHERE ts_vector @@ plainto_tsquery('english', $1)
OR (embedding <=> $2) < 0.5
ORDER BY score DESC
LIMIT $3
""",
query, embedding, limit,
)
else:
# Full-text search only
rows = await conn.fetch(
"""
SELECT path, content, metadata,
ts_rank(ts_vector, plainto_tsquery('english', $1)) AS score
FROM documents
WHERE ts_vector @@ plainto_tsquery('english', $1)
ORDER BY score DESC
LIMIT $2
""",
query, limit,
)
return [dict(row) for row in rows]
set_config(..., true) ma zakres transakcji, więc połączenie z puli nie może zachować kontekstu jednego tenanta dla kolejnego żądania. OR w pierwszej gałęzi zapewnia hybrydowość rozwiązania. COALESCE zachowuje w zbiorze wyników dokument dopasowany słowami kluczowymi, ale pozbawiony embeddingu, wraz z jego wynikiem tekstowym; nie wnosi podobieństwa wektorowego. Przy samym predykacie @@ dokument, który ma właściwe znaczenie, ale nie współdzieli żadnych słów kluczowych z zapytaniem, zostanie odfiltrowany, zanim w ogóle rozpocznie się scoring — to wyszukiwanie słów kluczowych z semantycznym rerankingiem, a nie wyszukiwanie hybrydowe. Wagi 0,6/0,4 są ilustracyjne: ranking tekstowy i podobieństwo cosinusowe mają różne skale. Normalizuj je na podstawie ewaluacji pobierania albo użyj fuzji rankingów, zanim zinterpretujesz te wagi jako względne znaczenie. Próg odległości jest pokrętłem: zaostrz go, jeśli ramię wektorowe zalewa wyniki, albo poluzuj, jeśli dopasowania semantyczne nigdy się nie pojawiają.
Poniższa regresja opisuje zachowanie, które należy przetestować na rzeczywistej bazie po migracjach. Przy tenant-a odczyt tenant-b zwraca zero wierszy, a bezpośredni insert między tenantami kończy się błędem RLS:
BEGIN;
SELECT set_config('app.tenant_id', 'tenant-a', true);
SELECT path FROM documents WHERE tenant_id = 'tenant-b'; -- 0 rows
INSERT INTO documents (tenant_id, path, content)
VALUES ('tenant-b', 'leak.md', 'must fail'); -- ERROR: row-level security policy
ROLLBACK;
Otrzymujesz:
- Wyszukiwanie hybrydowe: dopasowanie słów kluczowych (indeks GIN) + podobieństwo semantyczne (pgvector) oceniane wspólnie
- Wielu tenantów: tożsamość wynikająca z serwera oraz RLS egzekwowane przez bazę danych
- Gwarancje ACID: transakcje na primary commitują się atomowo; odczyty z repliki mogą być opóźnione
- Jeden system operacyjny: brak osobnej bazy wektorowej do zarządzania
- Skalowanie: repliki odczytowe mogą obsługiwać zapytania tolerujące nieaktualne dane. Natywne partycjonowanie może pomóc w eliminacji partycji i utrzymaniu, ale nie rozdziela zapisów między serwery; do tego potrzebny jest jawny projekt shardingu. Ścieżki read-after-write kieruj do primary albo zmierz odpowiednią politykę synchroniczną
Pliki świetnie sprawdzają się w przepływach pracy pojedynczego dewelopera. W produkcji wielotenantowej ustrukturyzowany magazyn dokumentów na PostgreSQL zwykle zapewnia najlepszy kompromis między prostotą, wydajnością i dojrzałością operacyjną.
Połączenie elementów: pełna architektura
Oto jak wszystkie trzy warstwy pamięci mogą współpracować w architekturze inspirowanej Market Analyst Agent. Diagram pokazuje ilustracyjny przepływ od żądania użytkownika do odpowiedzi, z aktywnymi wszystkimi warstwami pamięci.
Architektura ma trzy ścieżki pamięci:
-
Ścieżka hot (magazyn checkpointów): LangGraph zapisuje stan grafu możliwy do wznowienia w magazynie checkpointów na każdej granicy super-kroku. Gdy graf dociera do węzła
interrupt_before(takiego jak węzełpublishw Części 1), wykonanie zostaje wstrzymane. Użytkownik może zamknąć aplikację, a po powrocie graf wznowi działanie z checkpointu. Dzienniki zdarzeń runtime i trace’y to oddzielne kwestie produkcyjne. -
Ścieżka cold (magazyn długoterminowy): Po wybraniu trasy przez router planner odpytuje magazyn długoterminowy o istotny kontekst użytkownika. Planner nie może personalizować działania, dopóki ten odczyt się nie zakończy. Wyszukiwanie oparte na wektorach może obejmować embedding zapytania i pobieranie z indeksu; wyszukiwanie klucz-wartość nie. Nowe fakty można wyodrębnić i zapisać po zakończeniu rozmowy, dzięki czemu zapis nie opóźnia pętli rozumowania.
-
Ścieżka dokumentowa (magazyn plików): Podczas planowania agent odczytuje konwencje projektowe i notatki badawcze potrzebne do obsługi żądania. Podczas wykonania zapisuje podsumowania badań i poznane wzorce z powrotem na dysku. Odczyty te wpływają na bieżące zadanie, więc rozmiar plików, szybkość systemu plików i stan cache wpływają na czas odpowiedzi. Cache’uj tylko wtedy, gdy cache ma jasno zdefiniowane zasady unieważniania i izolacji tenantów. Zapisy mogą odbywać się później.
Podłączenie w LangGraph jest proste — magazyn checkpointów i magazyn długoterminowy są przekazywane podczas kompilacji grafu, a magazyn dokumentów jest wstrzykiwany jako zależność. Poniższy lokalny szkic kompiluje już skonfigurowany builder StateGraph wraz z węzłami przyjmującymi magazyn. Rozszerza to sposób podłączania grafu; helpery z Części 1 i projektu towarzyszącego create_graph nie przyjmują argumentu store. Używa InMemoryStore, aby zachować mały rozmiar fragmentu; referencyjna topologia Docker używa Qdrant do tej samej roli semantycznego przypominania.
import asyncio
from langgraph.store.memory import InMemoryStore
# Cold memory: local sketch with vector search
# (The reference Docker topology uses Qdrant for persistent recall.)
memory_store = InMemoryStore(
index={"dims": 1536, "embed": embedding_function}
)
# Document memory: illustrative file-based store for project knowledge
# FileMemory is the illustrative class defined above, not the project's current
# DocumentMemory implementation.
doc_memory = FileMemory(base_dir=".agent-memory")
async def main() -> None:
# Hot memory: PostgreSQL for durable checkpoints. postgres_checkpointer() is
# the async context manager defined earlier, so the graph runs inside it.
async with postgres_checkpointer(pg_connection_string) as checkpointer:
# builder is the configured StateGraph for this extended design.
# The Part 1/companion create_graph helper does not accept store.
graph = builder.compile(
checkpointer=checkpointer,
store=memory_store,
)
# ... run the graph here, while the connection is still open
asyncio.run(main())
# The store is accessible inside any node via the store parameter
def planner_node(state: AgentState, *, store: BaseStore) -> dict:
"""Plan with user context from long-term memory."""
# Recall relevant user facts from vector store.
# Namespace prefix is positional — see the store example above.
user_memories = store.search(
("user", state.user_id),
query=state.messages[-1].content,
limit=5,
)
# Load project conventions from document memory
conventions = doc_memory.read_doc("conventions/analysis-format.md")
# Inject both into planning context
# Each stored value is a dict; render whatever keys it carries
memory_context = "\n".join(str(m.value) for m in user_memories)
# ... rest of planning logic with personalized context and conventions
Pełny przepływ
W rozszerzonym projekcie powyżej żądanie powracającego użytkownika „Analyze TSLA” mogłoby przejść następujący przepływ. Semantyczne przypominanie i asynchroniczna ekstrakcja faktów są proponowanymi rozszerzeniami, a nie obecnym zachowaniem projektu towarzyszącego:
-
Ładowanie pamięci dokumentowej: Gdy uruchamia się planner, odczytuje z magazynu dokumentów konwencje projektowe: preferowany format analizy, preferowane źródła danych i wzorce użycia narzędzi. Ustanawiają one bazowe zachowanie dla tego planu.
-
Router: Router klasyfikuje żądanie jako
DEEP_RESEARCH. W tym przykładzie routing wykorzystuje samo żądanie, a nie preferencje długoterminowe. -
Przypominanie z pamięci cold + planner: Planner odpytuje magazyn długoterminowy wiadomością użytkownika. Pobiera informacje: „Użytkownik ma wysoką tolerancję ryzyka”, „Użytkownik preferuje szczegółową analizę konkurencji”, „Użytkownik wcześniej badał NVDA i AMD”. Następnie tworzy pięciostopniowy plan badań spersonalizowany zgodnie z tymi preferencjami. Uwzględnia krok analizy konkurencji, ponieważ historia użytkownika pokazuje, że tego oczekuje. Plan jest zgodny z formatem z dokumentu konwencji.
-
Pętla wykonawcza (pamięć hot): Każdy krok jest wykonywany według wzorca ReAct z Części 1 — myślenie, działanie, obserwacja, powtarzane do ukończenia kroku. LangGraph zapisuje checkpoint po każdym super-kroku (router, planner i każdy sekwencyjny krok executora w tym przykładzie). Odzyskiwanie rozpoczyna się od najnowszego utrwalonego checkpointu. Jeśli zapis kroku 3 został ukończony, graf może przejść do kroku 4; przy asynchronicznej trwałości awaria może wymagać powtórzenia ukończonego kroku.
-
Przerwanie HITL: Reporter zapisuje szkic. Oddzielna sesja modelu, bez historii tego uruchomienia, odczytuje szkic i zapisuje ocenę. Graf dociera następnie do
publish, gdzieinterrupt_beforewstrzymuje go niezależnie od tej oceny. Checkpoint przechowuje zarówno szkic, jak i ocenę, więc człowiek przegląda oba elementy przed podjęciem decyzji o publikacji. Kilka godzin później graf ponownie ładuje checkpoint i postępuje zgodnie z tą decyzją. -
Aktualizacje pamięci: Po zakończeniu rozmowy proces asynchroniczny wyodrębnia nowe fakty o użytkowniku („użytkownik śledzi teraz TSLA”, „użytkownik zaakceptował format raportu”) i zapisuje je w długoterminowym magazynie wektorowym. Agent zapisuje również podsumowanie badań w magazynie dokumentów (
research/TSLA-2026-02) do wykorzystania w przyszłości.
Wzorzec trzech warstw przejrzyście rozdziela odpowiedzialności. Magazyn checkpointów odpowiada za trwałość i wznowienie; jest infrastrukturą. Magazyn długoterminowy odpowiada za personalizację; jest logiką produktu. Magazyn dokumentów przechowuje zgromadzoną wiedzę projektową; jest notatnikiem agenta.
Kompromisy i kwestie do rozważenia
Pamięć przynosi wartość, ale dodaje również koszty i złożoność:
-
Koszt embeddingów: Każdy fakt zapisany w bazie wektorowej wymaga wygenerowania embeddingu. Hostowany dostawca embeddingów dodaje wywołanie API, koszt zależny od dostawcy i opóźnienie sieciowe; we wrześniu 2026 r. OpenAI podaje
text-embedding-3-smallw cenie $0.02 za milion tokenów. Koszt hostowanego modelu na fakt jest pomijalny, ale rośnie przy tysiącach użytkowników i sesji. Grupuj wywołania hostowane i cache’uj wyniki. W czasie zapytania wyszukiwanie wektorowe może obejmować embedding zapytania, indeks i opóźnienie sieci; wyszukiwanie klucz-wartość nie. Zmierz tę ścieżkę w swoim wdrożeniu, a następnie cache’uj typowe embeddingi zapytań lub użyj lokalnego modelu embeddingowego, jeśli opóźnienie ma znaczenie. -
Nieaktualna pamięć: Preferencje użytkownika się zmieniają. Fakt zapisany sześć miesięcy temu („użytkownik preferuje konserwatywne inwestycje”) może już nie być prawdziwy. Ustal politykę wygasania. Na przykład zespół może wygaszać preferencje po 365 dniach, a zdarzenia epizodyczne po 90 dniach, jeśli jego reguły prywatności, tempo aktualizacji i ewaluacja pobierania uzasadniają takie okna; są to proponowane zasady, a nie przenośne wartości domyślne. Wpis o inżynierii kontekstu odrzuca stałe reguły retencji jako przenośną politykę. Wygasanie jest podejściem uproszczonym. Stan typowany kierowany schematem oferuje precyzyjniejsze rozwiązanie: ważność czasową i pochodzenie każdego faktu, dzięki czemu wartość zastąpiona nowszą przegrywa z aktualną podczas pobierania, a nie dopiero w momencie wygaśnięcia.
-
Koszt pamięci w kontekście: Każdy przypomniany fakt zużywa tokeny w context window LLM. Jeśli dla każdego zapytania pobierzesz 20 faktów, pamięć kontekstowa zajmie kilkaset tokenów konkurujących z właściwym zadaniem. Ogranicz liczbę pobieranych faktów i priorytetyzuj je według wyniku trafności.
-
Prywatność i zgodność: Pamięć długoterminowa przechowuje dane użytkowników. Potrzebujesz anonimizacji PII przed zapisem, jasnych zasad retencji oraz kontroli po stronie użytkownika umożliwiających usuwanie danych. W branżach regulowanych żaden z tych elementów nie jest opcjonalny.
-
Wzrost magazynu checkpointów: Tabele checkpointów PostgreSQL rosną po każdym super-kroku. Nie uruchamiaj ogólnego zapytania SQL do przycinania: kanały delta mogą wymagać checkpointów przodków oraz powiązanych rekordów write/blob do odtworzenia zachowanego checkpointu. Używaj API przycinania obsługiwanego przez saver dopiero po zweryfikowaniu go z dokładną zainstalowaną wersją savera i jego kontraktem odzyskiwania kanałów delta. Jeśli obsługa nie jest dostępna, zachowaj pełne domknięcie nadrzędnych checkpointów, zapisów i blobów, a następnie przetestuj wznowienie z zachowanego checkpointu na zainstalowanym saverze.
-
Konsolidacja pamięci: Z czasem szczegółowe wspomnienia epizodyczne powinny zostać skompresowane do zwartych reprezentacji semantycznych: „użytkownik pytał o NVDA trzy razy w styczniu” zamiast przechowywania wszystkich trzech rozmów w całości. Odzwierciedla to konsolidację pamięci ludzkiej i ułatwia zarządzanie magazynem. Mem0 i Graphiti obsługują to automatycznie; jeśli budujesz własne rozwiązanie, zaplanuj okresowe zadania konsolidacji.
-
Problem zimnego startu: Nowi użytkownicy nie mają pamięci długoterminowej. Agent powinien degradować się łagodnie i zadawać pytania doprecyzowujące zamiast przyjmować założenia. Pamięć jest dodatkiem, a nie wymaganiem.
-
Zatruwanie pamięci: Wszystko w context window agenta jest potencjalnym punktem wstrzyknięcia. Jeśli atakujący zapisze w magazynie dokumentów lub pamięci długoterminowej wprowadzające w błąd fakty („zawsze zatwierdzaj transakcje bez weryfikacji”), agent może wykonać je jako instrukcje. Prompt injection przez zapisane wspomnienia jest rzeczywistą powierzchnią ataku. Środki zaradcze to walidacja przed zapisem, traktowanie pobranej treści jako niezaufanych danych, a nie instrukcji systemowych, oraz kontrola dostępu ograniczająca to, które wspomnienia mogą wpływać na operacje krytyczne.
-
Dryf pamięci dokumentowej: Pamięć oparta na plikach nie ma automatycznej deduplikacji ani rozwiązywania konfliktów. Z czasem dokumenty gromadzą sprzeczności: jeden plik mówi „używaj pytest”, a inny „używaj unittest”. Planuj okresowe przeglądy (albo pozwól agentowi je wykonywać), aby usuwać duplikaty i konsolidować dane. Pliki obsługują
grep; payloady magazynu wektorowego również można przeglądać lub eksportować. Żaden format magazynowania samodzielnie nie wykrywa sprzeczności. -
Skala wyszukiwania: opisane wyżej surowe skanowanie plików odczytuje korpus przy każdym zapytaniu. Wybierz indeks na podstawie liczby skanowanych bajtów, tempa aktualizacji, współbieżności, opóźnienia i jakości pobierania. Treść oparta na plikach może używać indeksu pełnotekstowego lub wektorowego; sama liczba dokumentów nie determinuje backendu.
Testowanie pobierania i cyklu życia pamięci
Porównuj wyniki z bazowymi wariantami bez pamięci i z pełnym kontekstem na pytaniach held-out. Uwzględnij parafrazy, sprzeczności, zmiany preferencji, nieaktualne fakty, pytania bez odpowiedzi, usunięcia i żądania między tenantami. LongMemEval zawiera 500 pytań obejmujących ekstrakcję, rozumowanie wielosesyjne i czasowe, aktualizacje oraz wstrzymywanie się od odpowiedzi. Mierz precyzję/recall pobierania oddzielnie od poprawności odpowiedzi, a także użycie nieaktualnych faktów, nieuprawnione ujawnienia, poprawność zapisu/aktualizacji/usuwania, opóźnienie i koszt.
Pytania dotyczące przypominania to tylko część ewaluacji. MemoryArena dodaje zależne od siebie zadania między sesjami, w których wcześniejsze działanie i jego informacja zwrotna muszą zmienić późniejsze zachowanie. Zadania obejmują zakupy, planowanie podróży, progresywne wyszukiwanie i rozumowanie formalne. Użyj tego projektu, gdy produkt obiecuje uczenie się na podstawie pracy, a nie tylko odpowiadanie na pytania dotyczące zapisanych rozmów. Są to zadania badawcze, a nie pomiary wdrożonej usługi pamięci.
EvoMemBench również rozdziela wiedzę od doświadczenia wykonawczego oraz pamięć wewnątrz epizodu od pamięci między epizodami. Porównanie 15 metod nie wskazuje jednej, konsekwentnie najsilniejszej formy pamięci; bazowe rozwiązania z długim kontekstem pozostają konkurencyjne w ramach tego protokołu. To argument za zachowaniem prostych bazowych wariantów w ewaluacji, a nie za zastępowaniem każdego magazynu najnowszym frameworkiem.
Przechowuj pochodzenie i ważność obok pobranych faktów. Wyniki ważności nie mogą ustanawiać zaufania ani zmieniać uprawnień. Polityka usuwania musi obejmować indeksy, cache’owane podsumowania i zachowane artefakty, a nie tylko oryginalny rekord.
Kolejna warstwa to działanie
Części 5 i 6 wracają do pamięci od strony operacyjnej, ale zajmują się różnymi jej połowami. Runtime odpowiada za checkpoint: miejsce zatrzymania wykonania i sposób jego ponownego uruchomienia. Harness odpowiada za przekazanie: znaczenie wykonanej pracy i to, co pozostało, zapisane jako pamięć dokumentowa dla kolejnej sesji modelu — jeden ciągły odcinek kontekstu modelu, zgodnie z terminologią doprecyzowaną w Części 5. Odtworzenie procesu nie jest tym samym co odtworzenie zadania.
Bibliografia
Artykuły
- Cognitive Architectures for Language Agents (CoALA) — Sumers, Yao i in., 2023 — Podstawowa taksonomia typów pamięci agenta
- Memory in the Age of AI Agents — grudzień 2025 — Kompleksowa, trójwymiarowa taksonomia pamięci agentów
- MemGPT: Towards LLMs as Operating Systems — Packer i in., 2023 — Wirtualne zarządzanie kontekstem dla agentów LLM
- Generative Agents: Interactive Simulacra of Human Behavior — Park i in., 2023 — Architektura strumienia pamięci z scoringiem świeżości, ważności i trafności
- Zep: A Temporal Knowledge Graph Architecture for Agent Memory — Rasmussen, 2025 — Dwuczasowy graf wiedzy dla pamięci agentów
- Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory — 2025 — Pipeline ekstrakcji i konsolidacji z benchmarkami
- Voyager: An Open-Ended Embodied Agent with Large Language Models — Wang i in., 2023 — Biblioteka umiejętności jako pamięć dokumentowa dla agentów gier w otwartym świecie
- JARVIS-1: Open-World Multi-task Agents with Memory-Augmented Multimodal Language Models — 2023 — Multimodalna biblioteka pamięci dla agentów Minecraft
- Agent Workflow Memory — Wang i in., 2024 — Wyprowadzanie przepływów pracy wielokrotnego użytku dla agentów automatyzacji webowej
- SkillWeaver: Web Agents can Self-Improve by Discovering and Honing Skills — 2025 — Samodzielnie syntezowane narzędzia API wielokrotnego użytku dla agentów webowych
- LEGOMem: Modular Procedural Memory for Multi-agent LLM Systems for Workflow Automation — październik 2025 — Wielokrotnego użytku moduły pamięci proceduralnej rozdzielone między orkiestrator i subagentów
Dokumentacja LangGraph
- LangGraph Persistence (Checkpointing) — Podstawowe pojęcia pamięci opartej na checkpointach
- LangGraph Memory Store — Pamięć długoterminowa między wątkami z interfejsem Store
- LangGraph Cross-Thread Persistence — Funkcjonalne API pamięci między wątkami
- How to add memory to the prebuilt ReAct agent — Praktyczny przewodnik dodawania pamięci
Backendy checkpointów
langgraph-checkpoint-postgres— Saver checkpointów PostgreSQL dla LangGraphlanggraph-checkpoint-redis— Saver checkpointów Redis dla LangGraph- LangGraph Redis Checkpoint 0.1.0 Redesign — Szczegóły architektury savera checkpointów Redis
langgraph-checkpoint-aws— Saver checkpointów DynamoDB z przenoszeniem danych do S3- Redis AI Agent Engineering — Wzorce Redis dla obciążeń agentowych
Bazy wektorowe i narzędzia pamięci
- Qdrant — Otwarta baza wektorowa z indeksowaniem HNSW i filtrowaniem
- Qdrant Agentic Builders Guide — Praktyczny przewodnik budowania pamięci agenta z Qdrant
- pgvector — Rozszerzenie PostgreSQL do wyszukiwania podobieństwa wektorowego
- Graphiti — Otwarty silnik czasowego grafu wiedzy firmy Zep
Pamięć dokumentowa i oparta na plikach
- Claude Code Memory — CLAUDE.md i katalog pamięci projektu
- Anthropic Memory Tool — Pamięć oparta na plikach po stronie klienta dla agentów Claude API
- Cursor Rules — Reguły projektu jako pliki .mdc w .cursor/rules
- Devin Desktop Memories — Reguły Cascade i automatycznie generowana pamięć lokalna dla workspace’u; domyślny Devin Local ich nie zapisuje
Frameworki pamięci
- Mem0 — Zarządzana warstwa pamięci z pipeline’em ekstrakcji i konsolidacji
- Letta (MemGPT) — Inspirowane systemem operacyjnym wirtualne zarządzanie kontekstem agentów
- LangMem SDK — Narzędzia zarządzania pamięcią dla LangGraph
Warsztaty
- MemAgents: Memory for LLM-Based Agentic Systems — Warsztaty ICLR 2026
Projekt demonstracyjny
- Market Analyst Agent — Referencyjna implementacja ścieżek checkpointów oraz bieżącego przechowywania profili i dokumentów