SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-13

مدیریت و پاکسازی حافظه قدیمی در Agentها

حافظه Agentها به مرور زمان دچار انحراف می‌شود. با تعیین تاریخ انقضا برای داده‌ها، اجرای دستورات حذف زنجیره‌ای در SQLite و بررسی دستی با sqlite3 از خطاهای مدل جلوگیری کنید.

چرا حافظه عامل (agent) قدیمی می‌شود

حافظه عامل به این دلیل قدیمی می‌شود که یک واقعیت (fact) یک‌بار نوشته شده و دیگر هرگز بررسی نمی‌شود. سیستم ذخیره‌سازی مدام آن را بازمی‌گرداند، لایه بازیابی آن را بدون هیچ تاریخچه‌ای به عنوان متن ساده در prompt قرار می‌دهد و مدل نیز آن را با همان اطمینانی تکرار می‌کند که در روز ثبتش داشت. هیچ خطایی هم رخ نمی‌دهد. مشکل اصلی همین است: از دید مدل و شما، یک حافظه قدیمی دقیقاً مشابه یک حافظه تازه به نظر می‌رسد.

بهبود کیفیت نوشتن در زمان ذخیره‌سازی، این مشکل را حل نمی‌کند. راه حل، تعیین تاریخ انقضا برای واقعیت‌هایی است که زمان‌مند هستند و ایجاد یک روال بازبینی برای واقعیت‌هایی که چنین نیستند. هر دو مورد، عملیات نگهداری معمول در یک پایگاه داده کوچک محسوب می‌شوند و بخش عمده کار شامل استفاده از SQL (زبان پرس‌وجوی ساختاریافته) است.

فرسایش و انحراف دو نوع شکست متفاوت هستند

فرسایش (Decay) حقیقتی است که تاریخ انقضای طبیعی دارد. «این هفته در سفر هستم.» «سرور staging برای مهاجرت خاموش است.» «در حال بررسی پیش‌نویس بودجه.» این موارد در زمان نگارش درست بوده‌اند و شما می‌توانید در همان لحظه، طول عمر آن‌ها را تعیین کنید. فرسایش قابل حل است. یک تاریخ انقضا، که گاهی TTL (زمان بقا) نامیده می‌شود، به آن اختصاص دهید و پس از گذشت آن، سطر مربوطه را حذف کنید.

انحراف (Drift) حقیقتی است که یک‌بار ذخیره شده و هرگز دوباره بررسی نمی‌شود. «از pnpm استفاده می‌کند.» «پایگاه داده Postgres 15 است.» «استقرارها از طریق شاخه staging انجام می‌شوند.» هیچ ساعتی باعث نادرست شدن این موارد نمی‌شود. بلکه تصمیمی در جای دیگری این کار را انجام می‌دهد و هیچ‌چیز به حافظهٔ ذخیره‌سازی شما در مورد آن اطلاع نمی‌دهد.

انحراف هیچ راهکار خودکار و تمیزی ندارد. یک سیستم ذخیره‌سازی نمی‌تواند تغییری را که هرگز مشاهده نکرده است تشخیص دهد، بنابراین وظیفه‌ای که صرفاً داده‌ها را می‌خواند و درباره آن‌ها استدلال می‌کند، فقط همان متن قدیمی را دوباره می‌خواند. مکانیزمی که کارساز است، تطبیق مجدد حقیقت با چیزی است که توصیف می‌کند؛ این کار نیازمند یک انسان یا عاملی است که ابزاری برای خواندن وضعیت فعلی در اختیار داشته باشد.

بنابراین برنامه به دو بخش تقسیم می‌شود. آنچه فرسایش می‌یابد را منقضی کنید. آنچه منحرف می‌شود را بازبینی کنید. با مشکل دوم طوری رفتار نکنید که گویی مشکل اول است.

تعیین تاریخ انقضا برای حقایق زمان‌دار

هر ردیف حافظه به سه ستونی نیاز دارد که اکثر ذخیره‌سازها ارائه نمی‌دهند: منبع حقیقت، زمان آخرین تأیید، و زمانی که حقیقت اعتبار خود را از دست می‌دهد. این ذخیره‌سازی است که می‌توانید تنها با sqlite3 بسازید، و همین ستون‌ها را می‌توان به ذخیره‌ساز فعلی خود نیز اضافه کرد.

CREATE TABLE memory (
  id            TEXT PRIMARY KEY,
  subject       TEXT NOT NULL,
  fact          TEXT NOT NULL,
  source        TEXT NOT NULL,
  created_at    TEXT NOT NULL DEFAULT (datetime('now')),
  confirmed_at  TEXT NOT NULL DEFAULT (datetime('now')),
  expires_at    TEXT,
  superseded_by TEXT REFERENCES memory(id) ON DELETE CASCADE
);

CREATE INDEX memory_expires ON memory(expires_at);
CREATE INDEX memory_superseded ON memory(superseded_by);

datetime('now') زمان UTC (زمان هماهنگ جهانی) را به صورت YYYY-MM-DD HH:MM:SS برمی‌گرداند که به عنوان متن به درستی مرتب و مقایسه می‌شود، بنابراین هر پرسش تاریخی در زیر یک عبارت ساده WHERE است. ستون source اختیاری نیست. حقیقتی که نتوانید آن را به یک پیام، فایل یا خروجی دستور ردیابی کنید، هرگز قابل بازبینی نیست و حقیقتی که قابل بازبینی نباشد، تنها باید حذف شود.

نوشتن حافظه‌ای که منقضی می‌شود:

INSERT INTO memory (id, subject, fact, source, expires_at)
VALUES ('m_0191', 'availability', 'Away from keyboard, replies are delayed',
        'chat 2026-08-08', datetime('now', '+7 days'));

بازیابی هرگز نباید مستقیماً جدول را بخواند. بازیابی باید از یک view استفاده کند که ردیف‌های منقضی‌شده و جایگزین‌شده را پنهان می‌کند:

CREATE VIEW live_memory AS
SELECT id, subject, fact, source, confirmed_at, expires_at
FROM memory
WHERE superseded_by IS NULL
  AND (expires_at IS NULL OR expires_at > datetime('now'));

این view نیمه مهم ماجراست، زیرا باعث می‌شود حذف‌نشدن ردیف‌ها (pruning) بی‌خطر باشد. یک ردیف منقضی‌شده در همان لحظه انقضا از نتایج بازیابی حذف می‌شود، فارغ از اینکه job حذف اجرا شده باشد یا خیر. در نتیجه، job حذف تنها مدیریت فضای دیسک و بار بازبینی را بر عهده دارد، نه صحت داده‌ها را.

شکاف را با sqlite3 memory.db "SELECT count(*) FROM memory;" و همان تعداد را در مقابل live_memory بررسی کنید. یک ذخیره‌ساز سالم دو عدد نزدیک به هم را نشان می‌دهد. شکاف بزرگ، نشان‌دهنده انباشت ردیف‌های مرده در سیستم شماست.

چرا حذف یک حافظه، مورد قدیمی را باقی می‌گذارد

اصلاحات به‌صورت جفت انجام می‌شوند. عامل یاد می‌گیرد که شما از npm به pnpm مهاجرت کرده‌اید، یک ردیف جدید می‌نویسد و ردیف قدیمی را به آن ارجاع می‌دهد:

UPDATE memory SET superseded_by = 'm_0207' WHERE id = 'm_0140';

ردیف قدیمی اکنون برای live_memory نامرئی است و زنجیره همچنان تغییرات را ثبت می‌کند. حالا m_0207 را حذف کنید، زیرا مشخص شده که اشتباه بوده است. ON DELETE CASCADE روی superseded_by باید m_0140 را نیز با خود ببرد، زیرا ردیف قدیمی در آن رابطه، فرزند محسوب می‌شود. معمولاً این اتفاق نمی‌افتد، زیرا SQLite کلیدهای خارجی (foreign keys) را نادیده می‌گیرد مگر اینکه آن‌ها را فعال کنید و حالت پیش‌فرض، غیرفعال است:

sqlite3 memory.db "PRAGMA foreign_keys;"

این دستور در یک build استاندارد، 0 را چاپ می‌کند. با غیرفعال بودن کلیدهای خارجی، DELETE FROM memory WHERE id = 'm_0207'; با موفقیت انجام می‌شود و m_0140 باقی می‌ماند و به شناسه‌ای اشاره می‌کند که دیگر وجود ندارد. هیچ هشداری دریافت نمی‌کنید. آن ردیف اکنون به دلیل اشتباهی پنهان شده است و اولین اسکریپت پاک‌سازی که اشاره‌گرهای معلق را به NULL بازنشانی می‌کند، عبارت "prefers npm" را مستقیماً به live_memory بازمی‌گرداند.

زنجیره‌های شکسته را پیدا کنید:

sqlite3 memory.db "PRAGMA foreign_key_check;"

foreign_key_check حتی زمانی که اعمال قوانین (enforcement) غیرفعال است، موارد نقض را گزارش می‌دهد، بنابراین روی آشفتگی‌هایی که از قبل دارید کار می‌کند. این دستور برای هر مورد نقض، یک ردیف چاپ می‌کند: جدول، rowid، جدول والد و اینکه کدام کلید خارجی شکست خورده است. خروجی خالی به این معنی است که زنجیره‌ها سالم هستند.

قانونی که در ادامه می‌آید کوتاه است. PRAGMA foreign_keys = ON; یک تنظیم برای هر اتصال (per connection) است، بنابراین هر اتصال به آن نیاز دارد: برنامه شما، اسکریپت پاک‌سازی شما و نشست sqlite3 که در آن تایپ می‌کنید. آن را به عنوان اولین خط در هر فایل SQL که عملیات حذف انجام می‌دهد، قرار دهید.

محل واقعی ذخیره خاطرات شما

پیش از حذف هر چیزی، بررسی کنید که چند محل ذخیره‌سازی دارید. یک سرویس حافظه self-hosted معمولاً متن خاطرات و embeddingهای آن را در یک پایگاه‌داده برداری (vector database) و لاگ تغییرات را در SQLite نگه می‌دارد. این‌ها فایل‌های متفاوتی با چرخه‌های عمر متفاوت هستند و ممکن است مستقل از یکدیگر دچار خطا شوند.

mem0 نمونه مناسبی است و ساختار مشابهی در سایر سرویس‌ها نیز دیده می‌شود. کتابخانه متن‌باز آن به‌صورت پیش‌فرض از Qdrant برای ذخیره‌سازی برداری در مسیر /tmp/qdrant و در مجموعه‌ای به نام mem0 استفاده می‌کند، به علاوه یک لاگ تغییرات SQLite در ~/.mem0/history.db که مکان آن از متغیر محیطی MEM0_DIR پیروی می‌کند. جدول history شامل memory_id، old_memory، new_memory، event، created_at و is_deleted است.

لیست ستون‌ها را دوباره بخوانید. فایل SQLite یک لاگ تغییرات است. خود خاطرات در Qdrant قرار دارند، بنابراین حذف ردیف‌ها از history.db فقط سابقه تغییرات را پاک می‌کند و خاطره همچنان قابل بازیابی باقی می‌ماند. عملیات حذف باید از طریق API (رابط برنامه‌نویسی اپلیکیشن) خود کتابخانه انجام شود تا هر دو محل به‌روزرسانی شوند:

from mem0 import Memory

memory = Memory()
memory.delete(memory_id="mem_123")
memory.delete_all(user_id="alice")

مقدار پیش‌فرض /tmp نیازمند هشدار ویژه‌ای است. در Ubuntu 24.10 و نسخه‌های بعد از آن، /tmp یک tmpfs است؛ یعنی یک سیستم فایل که در حافظه رم نگهداری می‌شود، بنابراین پس از هر بار reboot خالی شده و کل داده‌های ذخیره‌شده از بین می‌رود. وضعیت خود را با findmnt /tmp بررسی کنید. مشاهده خطی که شامل tmpfs باشد به این معنی است که باید همین امروز مسیر را تغییر دهید:

config = {
    "vector_store": {
        "provider": "qdrant",
        "config": {"collection_name": "mem0", "path": "/srv/agent/qdrant"},
    }
}
memory = Memory.from_config(config)

همین پرسش برای هر سرویسی که اجرا می‌کنید صادق است. فایل پیکربندی را بخوانید و تمام مسیرهایی که سرویس در آن‌ها می‌نویسد را یادداشت کنید. اجرای سرور حافظه mem0 روی VPS شخصی جنبه‌های مربوط به سرویس را پوشش می‌دهد و نگهداری حافظه عامل (agent) به‌صورت محلی روی یک ماشین به یک ذخیره‌ساز کوچک‌تر با همان نیازهای نگهداری می‌پردازد.

خواندن مخزن با استفاده از sqlite3

اگر رابط خط فرمان (CLI) نصب نیست، آن را با sudo apt install -y sqlite3 نصب کنید. سپس چهار دستور زیر به اکثر پرسش‌ها درباره هر مخزنی که روی دیسک دارید، پاسخ می‌دهند.

  • sqlite3 ~/.mem0/history.db ".tables" جداول را فهرست می‌کند. خروجی خالی به این معنی است که فایل اشتباهی را باز کرده‌اید.
  • sqlite3 ~/.mem0/history.db ".schema history" ستون‌های دقیق را چاپ می‌کند؛ این تنها مستندات قابل‌اعتماد از ساختار یک مخزن است.
  • sqlite3 -cmd ".mode line" ~/.mem0/history.db "SELECT * FROM history ORDER BY created_at DESC LIMIT 5;" پنج تغییر اخیر را به صورت هر فیلد در یک خط نمایش می‌دهد که باعث می‌شود حتی زمانی که یک ستون حاوی یک پاراگراف است، خوانایی حفظ شود.
  • sqlite3 ~/.mem0/history.db "SELECT event, count(*) FROM history GROUP BY event;" نشان می‌دهد که مخزن چه فعالیت‌هایی داشته و کتابخانه شما در واقع چه نام‌های رویدادی را ثبت می‌کند.

هر مخزن حافظه‌ای، یک پایگاه داده نیست. یک فایل متنی ساده که در ابتدای هر نشست خوانده می‌شود، هم با شکست مواجه می‌شود و هم هیچ‌کدام از ابزارهای لازم را ندارد: نه ستون انقضا، نه تاریخ تأییدشده و نه نمایی برای مخفی کردن ردیف‌های حذف‌شده. هر خطی را که به صورت دستی می‌نویسید تاریخ‌گذاری کنید و ماهانه آن را بازخوانی نمایید. حافظه‌ای که در نشست‌های Claude Code باقی می‌ماند همین مشکل را در مقیاسی کوچک‌تر دارد.

بازبینی حقایقی که منقضی نمی‌شوند

تغییرات (Drift) به یک صف، یک سقف و یک عادت نیاز دارند. صف شامل قدیمی‌ترین تأییدیه‌ها است:

SELECT id, subject, fact, source, confirmed_at
FROM live_memory
WHERE confirmed_at < datetime('now', '-90 days')
ORDER BY confirmed_at
LIMIT 20;

بازبینی 20 ردیف در هفته، کاری است که فرد واقعاً انجام می‌دهد. بازبینی 400 ردیف کاری است که هیچ‌کس انجام نمی‌دهد و شما را به نقطهٔ شروع بازمی‌گرداند. برای هر ردیف دو نتیجه وجود دارد. آن را با source خود دوباره بررسی کرده و مهر تأیید بزنید:

UPDATE memory SET confirmed_at = datetime('now') WHERE id = 'm_0140';

یا آن را جایگزین کنید: حقیقت جدید را درج کنید، مقدار superseded_by ردیف قدیمی را به شناسهٔ جدید تغییر دهید و اجازه دهید زنجیره، تاریخچه را حفظ کند.

دو عادت این کار را کم‌هزینه‌تر می‌کنند. مخزن را کوچک نگه دارید، زیرا مخزنی که فقط رشد می‌کند، بازبینی را غیرممکن می‌سازد: یک ستون last_used_at اضافه کنید، هنگام بازیابی واقعی یک ردیف آن را به‌روزرسانی کنید و ردیف‌هایی که برای 6 ماه استفاده نشده‌اند را به عنوان کاندیدای حذف در نظر بگیرید. این کار به ازای هر بازیابی یک عملیات نوشتن هزینه دارد، بنابراین اگر عامل (agent) پرحرف است، آن را به صورت دسته‌ای (batch) انجام دهید.

عادت دوم هیچ هزینه‌ای ندارد. سن (تاریخ) را در prompt قرار دهید. اگر بلوک حافظه‌ای که بازیاب شما می‌سازد، در کنار هر حقیقت confirmed 2026-05-02 را حمل کند، مدل می‌تواند به جای بیان قطعی، بگوید «از ماه مه شما از pnpm استفاده می‌کردید». حقیقتی که هیچ تاریخی به آن پیوست نشده باشد، برای مدل زبانی همیشه به زمان حال خوانده می‌شود.

اجرای عملیات prune طبق زمان‌بندی

عملیاتی که فقط در صورت یادآوری اجرا شود، عملاً اجرا نمی‌شود. دستور SQL را در /srv/agent/prune.sql قرار دهید:

PRAGMA foreign_keys = ON;

DELETE FROM memory
WHERE expires_at IS NOT NULL AND expires_at <= datetime('now');

DELETE FROM memory
WHERE superseded_by IS NOT NULL
  AND created_at < datetime('now', '-180 days');

فایل /etc/systemd/system/memory-prune.service را ذخیره کنید:

[Unit]
Description=Prune expired agent memories

[Service]
Type=oneshot
User=agent
ExecStart=/usr/bin/sqlite3 /srv/agent/memory.db ".read /srv/agent/prune.sql"

و /etc/systemd/system/memory-prune.timer:

[Unit]
Description=Run the agent memory prune daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now memory-prune.timer
systemctl list-timers memory-prune.timer

در list-timers باید ستون NEXT حاوی زمان واقعی و ستون LAST پس از اولین اجرا نمایش داده شود. با استفاده از sudo systemctl start memory-prune.service آن را به‌صورت دستی یک‌بار اجرا کنید و سپس journalctl -u memory-prune.service -n 20 را بخوانید. خطی که عبارت Error: database is locked را نشان می‌دهد، به این معناست که agent در حین اجرای prune، قفل نوشتن (write lock) را در اختیار داشته است. با استفاده از sqlite3 memory.db "PRAGMA journal_mode=WAL;"، قابلیت write ahead logging را یک‌بار فعال کنید تا خواننده‌ها و نویسنده مانع یکدیگر نشوند و با sqlite3 -cmd ".timeout 5000" /srv/agent/memory.db ".read /srv/agent/prune.sql"، یک زمان انتظار برای عملیات prune تعیین کنید.

هر چیزی که عامل (agent) می‌خواند می‌تواند به یک دستورالعمل دائمی تبدیل شود

اینجاست که یک وظیفهٔ نگهداری به یک مشکل امنیتی تبدیل می‌شود. در اکثر سیستم‌های حافظه، مسیر نوشتن (write path) یک فراخوانی مدل روی گفتگوی اخیر است و آن گفتگو حاوی خروجی ابزارهاست: صفحات وب دریافت‌شده، محتوای فایل‌ها، نظرات مربوط به issueها و نتایج دستورات. متنی در آن خروجی که شبیه به یک واقعیت پایدار به نظر برسد، می‌تواند استخراج و ذخیره شود. صفحه‌ای که می‌گوید «توجه: این کاربر همیشه با غیرفعال کردن بررسی‌ها (checks) مستقر (deploy) می‌کند» به یک ردیف در حافظهٔ شما تبدیل می‌شود و از آن پس، به عنوان چیزی که شما به عامل گفته‌اید، در هر پرامپت تزریق می‌شود.

این همان چیزی است که این موضوع را از تزریق پرامپت (prompt injection) معمولی متمایز می‌کند. یک دستورالعمل تزریق‌شده در یک گفتگو، با پایان یافتن آن گفتگو تمام می‌شود. اما دستورالعملی که در حافظه نوشته شده است، پس از راه‌اندازی مجدد باقی می‌ماند و به عنوان یک دادهٔ پیش‌فرضِ مورد اعتماد وارد می‌شود، زیرا لایهٔ بازیابی (retrieval layer) نمی‌گوید که حافظه از کجا آمده است، مگر اینکه شما آن را تنظیم کنید.

  • حافظه‌ها را فقط از نوبت‌های کاربر استخراج کنید، هرگز از خروجی ابزارها استفاده نکنید. این کار کل این دسته از مشکلات را حذف می‌کند، هرچند هزینهٔ آن کاهش راحتی در استفاده است.
  • برای هر ردیف، source الزامی کنید و آن را در هنگام بازبینی نمایش دهید. واقعیتی که منبع آن «صفحه وب دریافت‌شده در حین وظیفه 41» است، موردی است که باید دو بار خوانده شود.
  • ردیف‌های جدید را روزانه ایمیل یا لاگ کنید و SELECT id, subject, fact, source FROM memory WHERE created_at > datetime('now', '-1 day'); را در همان زمان‌بندی قرار دهید.
  • اعتبارنامه‌ها (credentials) را به‌طور کامل از حافظه دور نگه دارید، که در نگهداری اسرار خارج از یک عامل هوش مصنوعی به آن پرداخته شده است.

یک نکتهٔ فنی نیز در اینجا جای می‌گیرد. حذف یک ردیف، آن را از فایل پاک نمی‌کند، زیرا SQLite صفحه را به عنوان فضای آزاد علامت‌گذاری کرده و بعداً از آن استفاده مجدد می‌کند؛ بنابراین متن قدیمی تا زمانی که چیزی روی آن نوشته نشود، با strings memory.db قابل خواندن است. پس از حذف هرگونه اطلاعات حساس، sqlite3 memory.db "VACUUM;" را اجرا کنید که کل فایل را بازنویسی می‌کند. PRAGMA secure_delete = ON; باعث می‌شود اتصالی که عملیات حذف را انجام می‌دهد، محتوای آزادشده را در حین کار با صفر بازنویسی کند.

چه مواردی را و به چه ترتیبی پشتیبان‌گیری کنیم

فروشگاه کوچک است و بازسازی آن دشوار، بنابراین به‌درستی از آن پشتیبان تهیه کنید. هرگز یک فایل دیتابیس فعال را با cp کپی نکنید، زیرا کپی گرفته‌شده در حین نوشتن ممکن است باز نشود. از snapshot داخلی SQLite استفاده کنید:

sqlite3 /srv/agent/memory.db "VACUUM INTO '/srv/backup/memory-$(date +%F).db'"
sqlite3 /srv/backup/memory-$(date +%F).db "PRAGMA integrity_check;"

integrity_check چاپ کردن ok تنها مدرک قابل‌استناد برای اطمینان از سالم بودن فایل پشتیبان است. هر نتیجهٔ دیگری به این معناست که باید پشتیبان قبلی را نگه دارید و پیش از بازنویسی آن، علت را بررسی کنید.

در همان job و دقیقاً در همان زمان، از vector store نیز snapshot بگیرید. اگر این دو بخش با فاصلهٔ چند ساعت از هم ذخیره شوند، بازیابی آن‌ها باعث ترکیب شدن یک لاگ تغییرات جدید با مجموعه‌ای از حافظه‌های قدیمی می‌شود و حقایق حذف‌شده دوباره ظاهر خواهند شد. هر دو را در یک دایرکتوری تاریخ‌گذاری‌شده بنویسید تا فقط به‌صورت یکپارچه قابل بازیابی باشند. اجرای SQLite در محیط production روی یک VPS به جزئیات بیشتری دربارهٔ قفل‌گذاری، پشتیبان‌گیری و تنظیمات موردنیاز برای سرویس‌های طولانی‌مدت می‌پردازد.

FAQ

حافظهٔ عامل (agent) تا چه زمانی باید پیش از انقضا باقی بماند؟

زمان انقضا را بر اساس خودِ واقعیت تعیین کنید، نه یک مقدار پیش‌فرض سراسری. یک یادداشت سفر یا یادداشتی با عنوان «این هفته روی این پروژه کار می‌کنم» باید هفت روز اعتبار داشته باشد. یک قرارداد تیمی یا ترجیح شخصی نباید انقضا داشته باشد و در عوض باید به صف بازبینی منتقل شود. واقعیتی دربارهٔ نسخهٔ یک نرم‌افزار باید انقضایی تقریباً برابر با چرخهٔ انتشار (release cadence) آن پروژه داشته باشد. اگر در لحظهٔ نوشتن واقعیت نمی‌توانید عمر مفیدی برای آن تعیین کنید، این نشان‌دهندهٔ آن است که آن واقعیت به جای زوال، دچار تغییر تدریجی (drift) می‌شود؛ بنابراین به آن یک تاریخ confirmed_at اختصاص دهید و به‌جای منقضی کردن، آن را بازبینی کنید.

آیا می‌توانم به‌طور خودکار تشخیص دهم که یک واقعیت ذخیره‌شده نادرست شده است؟

به‌طور قابل‌اطمینان خیر. حافظه دیدی نسبت به دنیای خارج از خود ندارد، بنابراین نمی‌تواند تغییری که باعث نادرست شدن یک واقعیت شده را ببیند، و وظیفه‌ای (job) که حافظه را دوباره می‌خواند، فقط همان متن قدیمی را بازخوانی می‌کند. آنچه می‌توانید خودکار کنید، نمایان‌سازی است: بر اساس confirmed_at مرتب‌سازی کنید و قدیمی‌ترین ردیف‌ها را پیش روی یک انسان یا یک عامل قرار دهید که ابزاری برای خواندن وضعیت فعلی از یک مخزن (repository)، فایل پیکربندی یا endpoint مانیتورینگ در اختیار دارد. خودکارسازی صف ارزش انجام دادن دارد، اما خودکارسازی قضاوت هنوز به آن مرحله نرسیده است.

یک حافظه را حذف کردم اما دوباره برگشت. چرا؟

معمولاً به این دلیل که دو حافظه (store) وجود دارد و شما فقط در یکی از آن‌ها تغییر ایجاد کرده‌اید. متن حافظه و embedding آن معمولاً در یک پایگاه‌داده برداری (vector database) قرار دارند، در حالی که یک فایل SQLite لاگ تغییرات را نگه می‌دارد؛ بنابراین حذف ردیف‌ها از فایل SQLite فقط رکورد حسابرسی را پاک می‌کند و حافظه همچنان قابل بازیابی باقی می‌ماند. حذف را از طریق API کتابخانه انجام دهید تا هر دو به‌روزرسانی شوند. دلیل رایج دیگر، بازیابی (restore) است؛ جایی که از پایگاه‌داده برداری و فایل SQLite در زمان‌های متفاوتی snapshot گرفته شده است، بنابراین بازیابی باعث بازگشت ردیف‌هایی می‌شود که نیمهٔ دیگر قبلاً آن‌ها را حذف کرده بود.

آیا ویرایش دستی پایگاه‌دادهٔ حافظه در حین اجرای عامل ایمن است؟

خواندن ایمن است. نوشتن فقط در حالت write ahead logging ایمن است و حتی در آن حالت هم فقط یک نویسنده در هر لحظه مجاز است. دستور sqlite3 memory.db "PRAGMA journal_mode;" را اجرا کنید تا ببینید در چه حالتی هستید، و wal پاسخی است که به دنبال آن هستید. اگر Error: database is locked را مشاهده کردید، یعنی فرایند دیگری قفل نوشتن را در اختیار دارد؛ بنابراین با استفاده از sqlite3 -cmd ".timeout 5000" برای نشست خود وقفه ایجاد کنید یا ابتدا سرویس عامل را متوقف کنید. ویرایش دستی پایگاه‌داده برداری متفاوت است: این کار را به کتابخانه بسپارید، زیرا embedding و متن باید با یکدیگر سازگار باقی بمانند.