Rodzaje pamięci agenta AI a koszty utrzymania na VPS
Analiza pamięci semantycznej, epizodycznej i proceduralnej w systemach AI. Sprawdź zestawienie kosztów przechowywania danych oraz wydatków na re-embedding na serwerze VPS.
Trzy typy pamięci agenta
Typy pamięci agenta dzielą się na trzy kategorie, a każda z nich inaczej wpływa na wykorzystanie zasobów sprzętowych: pamięć semantyczna przechowuje fakty, pamięć epizodyczna rejestruje zdarzenia, a pamięć proceduralna zawiera instrukcje wykonania zadań. Poniższa tabela definiuje każdy z tych typów na przykładzie serwerowym. Dalsza część tekstu opisuje kwestie rzadziej poruszane: koszty przechowywania oraz koszty odtwarzania danych dla każdego typu pamięci.
The data behind this chart
[
{
"label": "Semantic",
"what_it_holds": "Facts the agent should treat as currently true",
"server_example": "The database listens on 10.8.0.4:5432 and the nightly dump runs at 03:15 UTC"
},
{
"label": "Episodic",
"what_it_holds": "A record of one past event or session",
"server_example": "On 2026-08-11 the deploy failed because the disk was full, and rotating logs fixed it"
},
{
"label": "Procedural",
"what_it_holds": "How to carry out a task, as steps the agent can run",
"server_example": "The restore runbook: stop the service, load the dump, run migrations, start the service"
}
]Nazwy te zostały zapożyczone z psychologii człowieka, przy czym jest to zapożyczenie luźne. Podział ten jest uzasadniony względami praktycznymi: trzy rodzaje pamięci mają różną objętość i wymagają odmiennych metod naprawy, dlatego umieszczenie ich wszystkich w jednym magazynie wektorowym obniża efektywność każdego z nich.
Pamięć semantyczna jest niewielka i wymaga ręcznej edycji
Kilkaset faktów dotyczących własnych serwerów zajmuje zaledwie kilkadziesiąt kilobajtów tekstu. Problem nie leży w pamięci masowej, lecz w poprawkach. Błędny fakt w pamięci semantycznej powoduje, że każda kolejna odpowiedź agenta będzie zawierać ten błąd. Magazyn danych musi zatem umożliwiać wyszukanie faktu po nazwie, jego zmianę oraz gwarantować usunięcie starej wartości.
Wskazuje to na magazyn klucz-wartość: tabelę w Postgres z kluczem głównym lub katalog małych plików markdown w git. Oba rozwiązania pozwalają na wykonanie pojedynczego zapytania, podgląd wartości i edycję w miejscu. Wyszukiwanie podobieństwa (similarity search) nie zapewnia takiej funkcjonalności, ponieważ pobieranie odbywa się na podstawie podobieństwa, a nie klucza. Polecenie „zmień port bazy danych” staje się wtedy „znajdź każdy fragment wspominający o porcie bazy danych”, przy czym nie można mieć pewności, że odnaleziono wszystkie wystąpienia. Fakty należy przechowywać z kluczami. Można je również osadzać (embed), jeśli wymagane jest luźne przywoływanie informacji, jednak to wersja z kluczem powinna stanowić źródło prawdy.
Nieaktualne fakty nie sygnalizują swojej nieprzydatności. Port ulega zmianie, a wiersz pozostaje, przez co agent nadal podaje numer, który był poprawny w czerwcu. Polityka nieaktualności i usuwania danych dla pamięci agenta stanowi drugą część tego zagadnienia i znacznie taniej jest ją zaprojektować, dopóki tabela jest jeszcze niewielka.
Dlaczego pamięć epizodyczna rośnie w nieskończoność
Pamięć epizodyczna to dziennik, a dzienniki rosną. Każda sesja, każde wywołanie narzędzia i każde nieudane polecenie są kandydatami na epizod. Agent zapisujący jeden wiersz na turę wygeneruje w miesiącu znacznie więcej danych, niż ktokolwiek będzie w stanie przeczytać. Dysk nie jest jedynym kosztem: każdy osadzony epizod trafia do indeksu, który musi przeszukiwać mechanizm wyszukiwania.
Zasady retencji należy ustalić w dniu tworzenia tabeli, gdy usuwanie danych jest jeszcze bezkosztowe. Dwa pytania pozwalają rozstrzygnąć większość wątpliwości. Po pierwsze: co w ogóle warto zapisywać? Podsumowanie sesji zazwyczaj jest wartościowe, pełne wyjście polecenia ls -la zazwyczaj nie. Po drugie: jak długo powinna żyć każda klasa epizodów? Przykładowo: surowe epizody przez 30 dni, a podsumowania sesji przez rok.
Każdemu wierszowi epizodu należy przypisać znacznik czasu created_at oraz kolumnę source. Bez created_at nie można usuwać danych według wieku. Bez source nie można usunąć wszystkich wpisów pochodzących z jednego błędnego źródła, co jest niezbędne w sytuacji, gdy okaże się, że strona internetowa lub zgłoszenie zapisywały instrukcje w pamięci.
DELETE FROM episodes WHERE created_at < now() - interval '30 days';Zadanie to należy uruchamiać za pomocą timera systemd, a następnie monitorować liczbę wierszy i rozmiar tabeli. Polityka retencji, której nikt nie wykonuje, jest jedynie komentarzem w kodzie.
psql -d agentmem -c "SELECT count(*) FROM episodes;"
psql -d agentmem -c "SELECT pg_size_pretty(pg_total_relation_size('episodes'));"Pamięć proceduralna powinna znajdować się w repozytorium
Pamięć proceduralna określa sposób wykonywania zadań przez agenta: skrypt powłoki, plik umiejętności lub runbook z ponumerowanymi krokami. Jest to kod, a kod powinien znajdować się tam, gdzie jego miejsce: w repozytorium git, z możliwością przeglądu, wersjonowania i czytania różnic (diff).
Przechowywanie runbooka w postaci osadzonych fragmentów prowadzi do uzyskania jedynie przybliżonej kopii. Mechanizm wyszukiwania zwraca fragmenty o najwyższym wyniku dopasowania, co sprawia, że agent może wykonać krok 2 i krok 5, podczas gdy krok 3 nigdy nie zostanie pobrany. Ponadto nic nie rejestruje, która wersja procedury została uruchomiona. W git git log odpowiada na oba te pytania. Koszt przechowywania jest bliski zeru, co stanowi kolejny powód, dla którego nie należy płacić cen za przechowywanie wektorowe w tym celu.
Rzeczywisty koszt wektora na dysku
The data behind this chart
[
{
"label": "bge-small-en-v1.5 (384 dims)",
"bytes_per_vector": 1544,
"mib_per_100k_rows": 147.2
},
{
"label": "bge-base-en-v1.5 (768 dims)",
"bytes_per_vector": 3080,
"mib_per_100k_rows": 293.7
},
{
"label": "bge-large-en-v1.5 (1024 dims)",
"bytes_per_vector": 4104,
"mib_per_100k_rows": 391.4
},
{
"label": "text-embedding-3-small (1536 dims)",
"bytes_per_vector": 6152,
"mib_per_100k_rows": 586.7
},
{
"label": "text-embedding-3-large (3072 dims)",
"bytes_per_vector": 12296,
"mib_per_100k_rows": 1172.6
}
]pgvector przechowuje vector jako 4 bajty na wymiar plus 8-bajtowy nagłówek. Obliczenia te są stałe, co pozwala na planowanie zapotrzebowania przed załadowaniem danych. Jeden wektor o 384 wymiarach zajmuje 1544 bajtów, co oznacza, że 100 000 fragmentów zajmuje 147.2 MiB. Ten sam korpus osadzony w 3 072 wymiarach zajmuje 1172.6 MiB przy 12296 bajtach na wiersz. Ten sam tekst, prawie ośmiokrotnie większe zużycie miejsca.
To tylko kolumna wektorowa. Tekst fragmentu, klucz główny, narzut wiersza oraz indeks stanowią dodatkowe obciążenie, a o indeksie często się zapomina. HNSW (hierarchical navigable small world, indeks grafowy budowany przez pgvector) przechowuje własną kopię powiązanych wektorów, więc zaindeksowany zbiór zajmuje znacznie ponad dwukrotność powyższych wartości. Zamiast szacować, należy przeprowadzić pomiary.
SELECT pg_size_pretty(pg_total_relation_size('memories')) AS total,
pg_size_pretty(pg_relation_size('memories_embedding_idx')) AS idx;Pamięć RAM decyduje o szybkości wyszukiwania, ponieważ graf jest szybki w przeszukiwaniu tylko wtedy, gdy znajduje się w pamięci. Gdy indeks jest większy niż pamięć, którą Postgres może na niego przeznaczyć, wyszukiwanie zaczyna odczytywać dane z dysku, co zwiększa opóźnienia. Proces budowania indeksu ma własny limit, maintenance_work_mem; gdy graf go przekroczy, proces zgłasza błąd i zwalnia:
NOTICE: hnsw graph no longer fits into maintenance_work_mem after 61440 tuples
HINT: Increase maintenance_work_mem to speed up builds.Istnieją dwa sposoby na zmniejszenie rozmiaru tego samego korpusu. Wybierz mniejszy model: 384 wymiary kosztują jedną czwartą tego, co 1 536 wymiarów, a w przypadku wyszukiwania własnych notatek różnica w dokładności jest często pomijalna. Można również przechowywać dane w połowicznej precyzji: typ halfvec zajmuje 2 bajty na wymiar plus ten sam 8-bajtowy nagłówek, co prawie o połowę zmniejsza rozmiar kolumny wraz z indeksem.
Przed wyborem modelu warto znać jeden limit. Według stanu na sierpień 2026 kolumna vector może być indeksowana do 2 000 wymiarów, więc osadzenie o 3 072 wymiarach zostanie przyjęte przez kolumnę, ale odrzucone przez indeks:
ERROR: column cannot have more than 2000 dimensions for hnsw indexhalfvec indeksuje do 4 000 wymiarów, więc typowym rozwiązaniem jest indeksowanie rzutowania:
CREATE INDEX ON memories USING hnsw ((embedding::halfvec(3072)) halfvec_cosine_ops);Kiedy pgvector przewyższa dedykowaną usługę pamięci
Jeśli na serwerze działa już Postgres, wektory to jeden pakiet i jedno polecenie.
psql -V
sudo apt install postgresql-16-pgvectorCREATE EXTENSION vector;Liczba w nazwie pakietu oznacza główną wersję Postgres, na przykład 16 w Ubuntu 24.04, dlatego przed wpisaniem polecenia należy zapoznać się z psql -V.
Przechowywanie pamięci w tej samej bazie danych zapewnia jedną kopię zapasową obejmującą zarówno pamięć, jak i dane aplikacji w tym samym momencie, jedną pulę połączeń oraz transakcje: fakt i opisujący go wiersz są zatwierdzane razem lub oba nie zostają zapisane. Osobna usługa nie może tego zagwarantować.
Należy przejść na dedykowaną usługę pamięci, gdy spełniony jest jeden z poniższych warunków. Obciążenie wyszukiwaniem konkuruje z aplikacją i wymaga własnej maszyny. Kilka agentów na różnych hostach współdzieli jedną pamięć. Lub wymagana jest logika ekstrakcji i deduplikacji, którą oferuje gotowy produkt, co stanowi argument za samodzielnie hostowanym serwerem pamięci Mem0. W przypadku jednego agenta i korpusu liczącego kilka milionów fragmentów, pgvector na już działającym serwerze jest łatwiejszy w utrzymaniu i mniej awaryjny. Wybór samego silnika oraz wymagania RAM dla każdego z nich opisano w uruchamianiu bazy danych wektorowych na VPS.
Koszty ponownej wektoryzacji przy zmianie modelu
Wektory pochodzące z dwóch różnych modeli nie są porównywalne, dlatego nie można przetworzyć nowych danych za pomocą nowego modelu, pozostawiając stare wiersze w niezmienionej formie. Mieszana tabela zwraca bezużyteczne wyniki, ponieważ odległość obliczona między dwoma różnymi układami współrzędnych jest liczbą pozbawioną znaczenia. Zmiana modelu oznacza konieczność ponownej wektoryzacji całego korpusu danych.
Ten koszt składa się z czterech elementów: tokenów (opłata za API lub czas pracy CPU i GPU na własnym serwerze), czasu rzeczywistego trwania procesu, miejsca na dysku dla obu kolumn jednocześnie podczas uzupełniania danych oraz końcowej przebudowy indeksu. Bezpieczna procedura polega na dodaniu nowej kolumny, uzupełnieniu jej danymi w partiach, przełączeniu zapytań na nową kolumnę, a następnie usunięciu starej kolumny wraz z jej indeksem.
Wydajność należy zmierzyć na własnym sprzęcie, zamiast polegać na opublikowanych danych, ponieważ wektoryzacja przy użyciu wyłącznie CPU na małym VPS przebiega znacznie wolniej niż w przypadku tego samego modelu uruchomionego na GPU. Należy zmierzyć czas przetwarzania reprezentatywnej próbki, a następnie pomnożyć go przez wielkość całego korpusu.
ollama pull nomic-embed-text
time curl -s http://localhost:11434/api/embed \
-d '{"model": "nomic-embed-text", "input": "one representative chunk of your corpus"}' > /dev/nullLokalny model wektoryzujący przechowuje swoje wagi na tym samym dysku co magazyn pamięci, a lokalizacja przechowywania modeli przez Ollama wyjaśnia, gdzie zajmowane jest to miejsce.
Jeden wymóg sprawia, że cały ten proces jest możliwy: należy przechowywać tekst źródłowy obok każdego wektora. Magazyn zawierający wyłącznie wektory nie może zostać poddany ponownej wektoryzacji, ponieważ nie ma danych wejściowych dla nowego modelu. Jeśli nie można odpowiedzieć na pytanie, jaki tekst wygenerował dany wiersz, ścieżką migracji pozostaje pełna odbudowa bazy z pierwotnego źródła danych.
Na co zwracać uwagę, gdy pamięć jest zapełniona
Koszt nie jest jedynym czynnikiem, który zmienia się wraz z zapełnianiem magazynu. Stare fakty tracą aktualność, a dawne epizody wypierają użyteczne wyniki, co ponownie sprowadza się do problemu przycinania danych. Magazyn pamięci stanowi również zapisywalne wejście wpływające na przyszłe zachowanie agenta, więc każdy element mający uprawnienia do zapisu może później sterować jego działaniem. Jeśli tekst ze stron internetowych lub zgłoszeń trafia do pamięci, należy zapoznać się z sposobem działania zatruwania pamięci agenta przed rozszerzeniem zakresu źródeł z uprawnieniami do zapisu. Rozmiar pobieranych danych wpływa również na zużycie tokenów przy każdym pojedynczym żądaniu i to właśnie od tego zaczyna się utrzymanie przewidywalnych kosztów działania agenta.
FAQ
Czy baza wektorowa jest potrzebna do obsługi pamięci agenta?
Nie w przypadku faktów. Pamięć semantyczna jest niewielka i wymaga poprawiania danych po nazwie, dlatego lepiej sprawdzają się tabele z kluczem lub katalog plików markdown w git, co pozwala na podgląd i edycję pojedynczych wartości. Wektory (embeddings) uzasadniają swój koszt, gdy konieczne jest wyszukiwanie według znaczenia w zbiorze zbyt dużym, by go przejrzeć, co zazwyczaj dotyczy pamięci epizodycznej i dokumentów. Jeśli używasz już Postgres, CREATE EXTENSION vector pozwala na realizację tego zadania bez dodawania kolejnej usługi do utrzymania.
Ile miejsca na dysku zajmie magazyn pamięci agenta?
Rozmiar wektorów jest przewidywalny i wynosi 4 bajty na wymiar plus 8 bajtów nagłówka w pgvector. Przy 768 wymiarach jest to 293.7 MiB na 100 000 wierszy, a przy 384 wymiarach 147.2 MiB. Należy doliczyć tekst fragmentów, narzut wiersza oraz indeks HNSW, który przechowuje własną kopię wektorów, dlatego należy zaplanować co najmniej dwukrotność rozmiaru wektorów i zmierzyć rzeczywiste zużycie za pomocą pg_total_relation_size.
Gdzie powinna znajdować się pamięć proceduralna?
W repozytorium git, w formie skryptów lub plików z umiejętnościami, które agent uruchamia bezpośrednio. Procedura wymaga dokładnego odtworzenia i historii wersji, czego wyszukiwanie podobieństwa nie zapewnia. Wyszukiwanie w podzielonym na fragmenty podręczniku zwraca najlepiej dopasowane części, co może skutkować otrzymaniem kroku 2 i 5 przy pominięciu kroku 3, bez żadnej informacji o tym, która wersja została wykonana.
Jaki jest koszt zmiany modelu embeddingów?
Wymaga to ponownego przetworzenia całego korpusu, ponieważ wektory z różnych modeli nie są ze sobą porównywalne. Należy uwzględnić koszt tokenów lub czas pracy GPU, miejsce na dysku dla starych i nowych kolumn w tym samym czasie oraz przebudowę indeksu. Należy dodać nową kolumnę, uzupełnić dane w partiach, przełączyć zapytania, a następnie usunąć starą kolumnę. Wszystko to zależy od przechowywania tekstu źródłowego obok każdego wektora.