এজেন্ট মেমোরি পুরনো হয়ে গেলে তা কীভাবে ডিলিট করবেন
এজেন্ট মেমোরি নীরবে ভুল তথ্য দিতে পারে। সময়সীমা থাকা তথ্যের জন্য এক্সপায়ারি সেট করুন, ডেটা ডিলিট করুন এবং sqlite3 ব্যবহার করে আপনার SQLite স্টোরটি নিয়মিত যাচাই করে দেখুন।
কেন এজেন্টের মেমোরি পুরনো (stale) হয়ে যায়
এজেন্টের মেমোরি পুরনো হয়ে যায় কারণ কোনো তথ্য একবার লেখা হলে তা আর কখনো যাচাই করা হয় না। স্টোর সবসময় সেই তথ্যই ফেরত দেয়, রিট্রিভাল লেয়ার কোনো তারিখ ছাড়াই সেটিকে প্লেইন টেক্সট হিসেবে প্রম্পটে যুক্ত করে এবং মডেলটি ঠিক সেই আত্মবিশ্বাসের সাথেই তথ্যটি পুনরাবৃত্তি করে যা এটি লেখার দিন ছিল। কোনো কিছুই কোনো এরর (error) তৈরি করে না। এটাই মূল সমস্যা: মডেল এবং আপনার কাছে একটি পুরনো মেমোরি দেখতে একদম নতুন মেমোরির মতোই মনে হয়।
তথ্য সেভ করার সময় ভালো করে লিখলে এই সমস্যার সমাধান হয় না। এর সমাধান হলো যেসব তথ্যের মেয়াদ আছে সেগুলোর জন্য একটি এক্সপায়ারি (expiry) সেট করা এবং যেসব তথ্যের মেয়াদ নেই সেগুলোর জন্য একটি নিয়মিত রিভিউ রুটিন রাখা। ছোট ডেটাবেসের ক্ষেত্রে এগুলো সাধারণ রক্ষণাবেক্ষণের কাজ এবং এর বেশিরভাগই SQL (structured query language) দিয়ে সম্পন্ন করতে হয়।
Decay এবং drift হলো ভিন্ন ধরনের ব্যর্থতা
Decay বা ক্ষয় হলো এমন একটি তথ্য যার একটি নির্দিষ্ট মেয়াদ শেষ হওয়ার তারিখ থাকে। যেমন: "এই সপ্তাহে ভ্রমণে আছি।" "মাইগ্রেশনের জন্য staging বক্সটি বন্ধ আছে।" "বাজেটের খসড়া পর্যালোচনা করছি।" এগুলো লেখার সময় সত্য ছিল এবং আপনি লেখার মুহূর্তেই এর স্থায়িত্বকাল নির্ধারণ করতে পারেন। Decay সমাধানযোগ্য। এর সাথে একটি মেয়াদ বা TTL (time to live) যুক্ত করুন এবং সময় পার হয়ে গেলে সেই তথ্য মুছে ফেলুন।
Drift বা বিচ্যুতি হলো এমন একটি তথ্য যা একবার সংরক্ষণ করা হয়েছে কিন্তু আর কখনো যাচাই করা হয়নি। যেমন: "pnpm পছন্দ করে।" "ডাটাবেসটি Postgres 15।" "staging branch দিয়ে deploy করা হয়।" কোনো ঘড়ি এই তথ্যগুলোকে মিথ্যা প্রমাণ করতে পারে না। অন্য কোথাও নেওয়া কোনো সিদ্ধান্ত এগুলোকে মিথ্যা করে দেয়, কিন্তু আপনার মেমোরি স্টোর সেই পরিবর্তন সম্পর্কে কিছুই জানে না।
Drift-এর কোনো সহজ স্বয়ংক্রিয় সমাধান নেই। একটি স্টোর এমন কোনো পরিবর্তন শনাক্ত করতে পারে না যা সে কখনো পর্যবেক্ষণ করেনি। তাই যে job স্টোরটি পড়ে এবং তা নিয়ে কাজ করে, সেটি কেবল সেই পুরনো তথ্যই বারবার পড়ছে। কার্যকর পদ্ধতি হলো তথ্যের উৎস বা যে বিষয়টিকে এটি বর্ণনা করছে, তার সাথে পুনরায় যাচাই করা। এর জন্য একজন মানুষ অথবা এমন কোনো agent প্রয়োজন যার কাছে বর্তমান অবস্থা পড়ার মতো টুল আছে।
তাই পরিকল্পনাটি দুই ভাগে বিভক্ত। যা ক্ষয় হয় (decay) তার মেয়াদ শেষ করুন। যা বিচ্যুত হয় (drift) তা পর্যালোচনা করুন। দ্বিতীয় সমস্যাটিকে প্রথমটির মতো বিবেচনা করবেন না।
সময়-নির্ভর তথ্যের জন্য মেয়াদ নির্ধারণ করুন
প্রতিটি মেমরি রো-এর জন্য এমন তিনটি কলাম প্রয়োজন যা অধিকাংশ স্টোরে থাকে না: তথ্যটি কোথা থেকে এসেছে, কখন এটি সর্বশেষ নিশ্চিত করা হয়েছিল এবং কখন এটি সত্য থাকা বন্ধ করে দেয়। এটি এমন একটি স্টোর যা আপনি শুধুমাত্র 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 (coordinated universal time) সময়কে 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'));এই ভিউটিই গুরুত্বপূর্ণ অংশ, কারণ এটি কোনো কারণে prune বা মুছে ফেলার কাজ বাদ পড়লেও কোনো ক্ষতি হতে দেয় না। একটি মেয়াদোত্তীর্ণ রো তার মেয়াদ শেষ হওয়ার মুহূর্ত থেকেই আর রিট্রিভ বা পুনরুদ্ধার করা হয় না, ডিলিট জবটি চলুক বা না চলুক। ডিলিট জবটি তখন শুধুমাত্র ডিস্কের ব্যবহার এবং রিভিউ লোড নিয়ন্ত্রণ করে, তথ্যের সঠিকতা নয়।
sqlite3 memory.db "SELECT count(*) FROM memory;" ব্যবহার করে গ্যাপ বা ব্যবধান পরীক্ষা করুন এবং live_memory-এর সাথে একই কাউন্ট তুলনা করুন। একটি সুস্থ স্টোরে এই দুটি সংখ্যা কাছাকাছি থাকে। বড় ব্যবধান মানে হলো আপনার মৃত রো-গুলোর ব্যাকলগ জমে আছে।
কেন একটি মেমরি মুছে ফেলার পরেও পুরনোটি থেকে যায়
সংশোধনগুলো জোড়ায় জোড়ায় আসে। এজেন্ট যখন দেখে আপনি npm থেকে pnpm-এ সরে এসেছেন, তখন সে একটি নতুন সারি (row) লেখে এবং পুরনো সারিটিকে সেটির দিকে নির্দেশ করে:
UPDATE memory SET superseded_by = 'm_0207' WHERE id = 'm_0140';পুরনো সারিটি এখন live_memory-এর কাছে অদৃশ্য, এবং চেইনটি রেকর্ড করে রাখে কী পরিবর্তিত হয়েছে। এখন m_0207 মুছে ফেলুন, কারণ এটি ভুল বলে প্রমাণিত হয়েছে। superseded_by-এর ওপর থাকা ON DELETE CASCADE-এর উচিত m_0140-কে সাথে নিয়ে মুছে যাওয়া, কারণ সেই সম্পর্কের ক্ষেত্রে পুরনো সারিটি হলো চাইল্ড। সাধারণত এটি ঘটে না, কারণ SQLite ফরেন কি (foreign keys) উপেক্ষা করে যদি না আপনি সেগুলো চালু করেন, আর ডিফল্টভাবে সেগুলো বন্ধ থাকে:
sqlite3 memory.db "PRAGMA foreign_keys;"এটি একটি স্টক বিল্ডে 0 প্রিন্ট করে। ফরেন কি বন্ধ থাকলে, DELETE FROM memory WHERE id = 'm_0207'; সফল হয় এবং m_0140 থেকে যায়, যা এমন একটি id-কে নির্দেশ করে যার অস্তিত্ব নেই। কোনো কিছুই আপনাকে সতর্ক করবে না। সেই সারিটি এখন ভুল কারণে লুকানো অবস্থায় আছে, এবং প্রথম যে টাইডি-আপ স্ক্রিপ্টটি ড্যাংলিং পয়েন্টারগুলোকে NULL-এ রিসেট করে, সেটি "prefers npm" কথাটিকে সরাসরি live_memory-এ ফিরিয়ে আনে।
ভাঙ্গা চেইনগুলো খুঁজে বের করুন:
sqlite3 memory.db "PRAGMA foreign_key_check;"foreign_key_check এনফোর্সমেন্ট বন্ধ থাকলেও ভায়োলেশন বা নিয়মভঙ্গ রিপোর্ট করে, তাই এটি আপনার বর্তমান অগোছালো ডেটার ওপরও কাজ করে। এটি প্রতিটি ভায়োলেশনের জন্য একটি করে সারি প্রিন্ট করে: টেবিল, rowid, প্যারেন্ট টেবিল এবং কোন ফরেন কি ব্যর্থ হয়েছে তা। আউটপুট খালি থাকা মানে চেইনগুলো অক্ষত আছে।
এর পরের নিয়মটি সংক্ষিপ্ত। PRAGMA foreign_keys = ON; হলো প্রতি কানেকশনের জন্য একটি সেটিং, তাই প্রতিটি কানেকশনের জন্য এটি প্রয়োজন: আপনার অ্যাপ্লিকেশন, আপনার প্রুন স্ক্রিপ্ট এবং যে sqlite3 সেশনে আপনি টাইপ করছেন। যেকোনো কিছু মুছে ফেলে এমন প্রতিটি SQL ফাইলের প্রথম লাইনে এটি যুক্ত করুন।
আপনার মেমোরিগুলো আসলে কোথায় থাকে
কোনো কিছু মুছে ফেলার আগে, আপনার কতগুলো স্টোর আছে তা খুঁজে বের করুন। একটি self-hosted মেমোরি সার্ভিস সাধারণত মেমোরির টেক্সট এবং এর এমবেডিং একটি vector database-এ রাখে এবং একটি change log রাখে SQLite-এ। এগুলো ভিন্ন ভিন্ন ফাইল এবং এদের lifecycle-ও আলাদা, তাই এগুলো একে অপরের থেকে বিচ্ছিন্নভাবে ব্যর্থ হতে পারে।
mem0 একটি উপযুক্ত উদাহরণ, এবং একই কাঠামো অন্য জায়গাতেও দেখা যায়। এর open source লাইব্রেরি ডিফল্টভাবে /tmp/qdrant-এ Qdrant vector store ব্যবহার করে, যার collection-এর নাম mem0, এবং এর পাশাপাশি ~/.mem0/history.db-এ একটি SQLite change log রাখে, যার অবস্থান MEM0_DIR environment variable দ্বারা নির্ধারিত হয়। history টেবিলে memory_id, old_memory, new_memory, event, created_at এবং is_deleted থাকে।
কলামের তালিকাটি আবার পড়ুন। SQLite ফাইলটি একটি change log। মেমোরিগুলো মূলত Qdrant-এ থাকে, তাই history.db থেকে সারি মুছে ফেললে কেবল পরিবর্তনের রেকর্ডটি মুছে যায় কিন্তু মেমোরিটি পুনরুদ্ধারযোগ্য থেকে যায়। মুছে ফেলার কাজগুলো অবশ্যই লাইব্রেরির নিজস্ব API (application programming interface)-এর মাধ্যমে করতে হবে যাতে উভয় জায়গাই আপডেট হয়:
from mem0 import Memory
memory = Memory()
memory.delete(memory_id="mem_123")
memory.delete_all(user_id="alice")/tmp ডিফল্টটির জন্য আলাদা সতর্কবার্তা প্রয়োজন। Ubuntu 24.10 এবং পরবর্তী ভার্সনগুলোতে, /tmp হলো একটি tmpfs, যা মেমোরিতে থাকা একটি filesystem, তাই প্রতিটি reboot-এর পর এটি খালি হয়ে যায় এবং পুরো স্টোরটি মুছে যায়। findmnt /tmp দিয়ে আপনারটি পরীক্ষা করুন। যদি কোনো লাইনে tmpfs দেখা যায়, তবে আজই পাথটি সরিয়ে ফেলুন:
config = {
"vector_store": {
"provider": "qdrant",
"config": {"collection_name": "mem0", "path": "/srv/agent/qdrant"},
}
}
memory = Memory.from_config(config)আপনি যা-ই চালান না কেন, একই প্রশ্ন প্রযোজ্য। কনফিগারেশন পড়ুন এবং সার্ভিসটি যে যে পাথে লেখে তার একটি তালিকা তৈরি করুন। আপনার নিজস্ব VPS-এ mem0 মেমোরি সার্ভার চালানো এই সার্ভিসের দিকটি কভার করে, এবং এজেন্ট মেমোরিকে একটি মেশিনে সীমাবদ্ধ রাখা হলো একই রক্ষণাবেক্ষণের প্রয়োজনসহ একটি ছোট স্টোর।
sqlite3 দিয়ে স্টোর পড়া
যদি CLI (command line interface) ইনস্টল করা না থাকে, তবে 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;"স্টোরটি কী কাজ করছে এবং আপনার লাইব্রেরি আসলে কোন কোন ইভেন্টের নাম লিখছে তা দেখায়।
সব মেমোরি স্টোর ডেটাবেস নয়। প্রতিটি সেশনের শুরুতে পড়া সাধারণ নোট ফাইলগুলোতে যেমন ব্যর্থতার ঝুঁকি থাকে, তেমনি কোনো টুলিং সুবিধাও থাকে না: এতে কোনো expiry কলাম নেই, নিশ্চিত তারিখ নেই, এবং অকার্যকর সারিগুলো লুকিয়ে রাখার কোনো উপায় নেই। হাতে লেখা প্রতিটি লাইনে তারিখ দিন এবং প্রতি মাসে সেগুলো পুনরায় পড়ুন। Claude Code সেশনগুলোতে যে মেমোরি বজায় থাকে তার সমস্যাটিও একই, শুধু এর পরিধি ছোট।
যেসব তথ্য মেয়াদোত্তীর্ণ হয় না তা পর্যালোচনা করা
Drift-এর জন্য একটি queue, একটি cap এবং একটি অভ্যাসের প্রয়োজন। queue হলো সবচেয়ে পুরনো নিশ্চিতকরণগুলো:
SELECT id, subject, fact, source, confirmed_at
FROM live_memory
WHERE confirmed_at < datetime('now', '-90 days')
ORDER BY confirmed_at
LIMIT 20;সপ্তাহে বিশটি row পর্যালোচনা করা এমন একটি কাজ যা বাস্তবে করা সম্ভব। চারশটি row পর্যালোচনা করা অসম্ভব, যা আপনাকে শুরুর অবস্থানেই ফিরিয়ে নিয়ে যাবে। প্রতিটি row-এর জন্য দুটি ফলাফল থাকে। সেটিকে তার source-এর বিপরীতে পুনরায় যাচাই করুন এবং stamp করুন:
UPDATE memory SET confirmed_at = datetime('now') WHERE id = 'm_0140';অথবা এটিকে প্রতিস্থাপন করুন: নতুন তথ্যটি insert করুন, পুরনো row-এর superseded_by-কে নতুন id-তে সেট করুন এবং chain-টিকে ইতিহাস ধরে রাখতে দিন।
দুটি অভ্যাস এই প্রক্রিয়াটিকে সহজতর করে। store-টিকে ছোট রাখুন, কারণ কেবল বৃদ্ধি পেতে থাকা store পর্যালোচনা করা অসম্ভব করে তোলে: একটি last_used_at কলাম যোগ করুন, যখন কোনো row প্রকৃতপক্ষে retrieve করা হয় তখন সেটিকে আপডেট করুন এবং ছয় মাস ধরে অব্যবহৃত row-গুলোকে মুছে ফেলার জন্য বিবেচনা করুন। এটি প্রতি retrieval-এ একটি write খরচ করে, তাই agent যদি খুব বেশি সক্রিয় হয় তবে সেগুলোকে batch করুন।
দ্বিতীয় অভ্যাসটির কোনো খরচ নেই। prompt-এ বয়স উল্লেখ করুন। আপনার retriever যে memory block তৈরি করে তাতে যদি প্রতিটি তথ্যের পাশে confirmed 2026-05-02 থাকে, তবে model-টি সরাসরি তথ্য না জানিয়ে বলতে পারবে "মে মাস পর্যন্ত আপনি pnpm ব্যবহার করছিলেন"। তারিখবিহীন কোনো তথ্য language model-এর কাছে প্রতিবারই বর্তমান কালের (present tense) মতো মনে হয়।
একটি নির্দিষ্ট সময়সূচী অনুযায়ী prune চালানো
আপনার মনে পড়ার ওপর ভিত্তি করে 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.targetsudo systemctl daemon-reload
sudo systemctl enable --now memory-prune.timer
systemctl list-timers memory-prune.timerlist-timers-এ একটি NEXT কলাম দেখানো উচিত যেখানে প্রকৃত সময় থাকবে, এবং প্রথমবার চালানোর পর একটি LAST কলাম থাকবে। sudo systemctl start memory-prune.service ব্যবহার করে হাতে কলমে এটি একবার ট্রিগার করুন, তারপর journalctl -u memory-prune.service -n 20 পড়ুন। Error: database is locked লেখা একটি লাইন মানে হলো 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-কে একটি wait দিন।
এজেন্ট যা কিছু পড়ে তা স্থায়ী নির্দেশনায় পরিণত হতে পারে
এখানেই একটি সাধারণ রক্ষণাবেক্ষণ কাজ নিরাপত্তা ঝুঁকিতে পরিণত হয়। বেশিরভাগ মেমোরি সিস্টেমে রাইট পাথ (write path) হলো সাম্প্রতিক কথোপকথনের ওপর একটি মডেল কল, এবং সেই কথোপকথনে টুলের আউটপুট থাকে: যেমন ওয়েব পেজ, ফাইলের বিষয়বস্তু, ইস্যু কমেন্ট বা কমান্ডের ফলাফল। সেই আউটপুটে থাকা টেক্সট যা স্থায়ী তথ্যের মতো দেখায়, তা এক্সট্রাক্ট করে সংরক্ষণ করা হতে পারে। কোনো পেজে যদি লেখা থাকে "দ্রষ্টব্য: এই ব্যবহারকারী সবসময় চেক ডিজেবল করে ডেপ্লয় করেন", তবে তা আপনার স্টোরে একটি সারি (row) হিসেবে জমা হবে এবং এরপর থেকে প্রতিবার প্রম্পটে এটি এমনভাবে ইনজেক্ট করা হবে যেন আপনিই এজেন্টকে এটি বলেছেন।
সাধারণ প্রম্পট ইনজেকশন থেকে এটি এখানেই আলাদা। একটি কথোপকথনের ভেতরে ইনজেক্ট করা নির্দেশনা কথোপকথন শেষ হওয়ার সাথে সাথেই শেষ হয়ে যায়। কিন্তু মেমোরিতে লেখা একটি ইনজেক্ট করা নির্দেশনা রিস্টার্টের পরেও টিকে থাকে এবং আগে থেকেই বিশ্বস্ত হিসেবে গণ্য হয়, কারণ রিট্রিভাল লেয়ার (retrieval layer) নিজে থেকে বলে দেয় না যে মেমোরিটি কোথা থেকে এসেছে।
- শুধুমাত্র ব্যবহারকারীর ইনপুট থেকে মেমোরি এক্সট্রাক্ট করুন, টুলের আউটপুট থেকে কখনোই নয়। এটি এই ধরনের ঝুঁকি পুরোপুরি দূর করে, যদিও এতে কিছুটা সুবিধা কমতে পারে।
- প্রতিটি সারিতে
sourceথাকা বাধ্যতামূলক করুন এবং রিভিউ করার সময় তা প্রদর্শন করুন। "টাস্ক 41 চলাকালীন ফেচ করা ওয়েব পেজ" থেকে আসা তথ্য দুবার যাচাই করা উচিত। - প্রতিদিন নতুন সারিগুলো ইমেইল বা লগ করুন, সাথে
SELECT id, subject, fact, source FROM memory WHERE created_at > datetime('now', '-1 day');একই টাইমারের মধ্যে রাখুন। - স্টোর থেকে ক্রেডেনশিয়াল পুরোপুরি দূরে রাখুন, যা keeping secrets out of an AI agent-এ বিস্তারিত আলোচনা করা হয়েছে।
একটি যান্ত্রিক বিষয়ও এখানে উল্লেখ করা প্রয়োজন। কোনো সারি ডিলিট করলেই তা ফাইল থেকে মুছে যায় না, কারণ SQLite পেজটিকে খালি হিসেবে চিহ্নিত করে এবং পরে পুনরায় ব্যবহার করে। তাই strings memory.db দিয়ে পুরনো টেক্সট তখনও পড়া সম্ভব যতক্ষণ না অন্য কিছু দিয়ে তা ওভাররাইট করা হচ্ছে। সংবেদনশীল কিছু মুছে ফেলার পর sqlite3 memory.db "VACUUM;" চালান, যা পুরো ফাইলটি নতুন করে লেখে। PRAGMA secure_delete = ON; ব্যবহার করলে ডিলিট করার সময় কানেকশনটি খালি হওয়া কন্টেন্টগুলোকে জিরো (zeros) দিয়ে ওভাররাইট করে দেয়।
কী কী ব্যাকআপ নিতে হবে এবং কোন ক্রমে নিতে হবে
স্টোরটি ছোট এবং এটি পুনরায় তৈরি করা কঠিন, তাই সঠিকভাবে ব্যাকআপ নিন। cp ব্যবহার করে কখনোই লাইভ ডাটাবেস ফাইল কপি করবেন না, কারণ লেখার সময় নেওয়া কপিটি ওপেন নাও হতে পারে। 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 প্রিন্ট করা হলো একমাত্র প্রমাণ যে একটি ব্যাকআপ ফাইল ব্যবহারযোগ্য। অন্য যেকোনো ক্ষেত্রে আগের ব্যাকআপটি রেখে দিন এবং সেটি ওভাররাইট করার আগে তদন্ত করুন।
একই সময়ে, একই জবে ভেক্টর স্টোরের স্ন্যাপশট নিন। যদি দুটি অংশ কয়েক ঘণ্টা ব্যবধানে ক্যাপচার করা হয়, তবে রিস্টোর করার সময় নতুন চেঞ্জ লগ এবং পুরনো মেমোরি সেট মিশে যাবে, ফলে মুছে ফেলা তথ্যগুলো আবার ফিরে আসবে। উভয় ফাইলকে একটি তারিখযুক্ত ডিরেক্টরিতে রাখুন যাতে সেগুলো শুধুমাত্র একসাথে রিস্টোর করা যায়। VPS-এ প্রোডাকশন পরিবেশে SQLite চালানো অংশে লকিং, ব্যাকআপ এবং দীর্ঘ সময় ধরে চলা সার্ভিসের প্রয়োজনীয় সেটিংস সম্পর্কে বিস্তারিত আলোচনা করা হয়েছে।
FAQ
এজেন্টের মেমরি এক্সপায়ার হওয়ার আগে কতদিন থাকা উচিত?
গ্লোবাল ডিফল্ট থেকে নয়, বরং তথ্যের ধরন অনুযায়ী এক্সপায়ারি সেট করুন। ভ্রমণের নোট বা "এই সপ্তাহে এই প্রজেক্টে কাজ করছি" জাতীয় নোটের জন্য সাত দিন সময় নির্ধারণ করুন। দলের কোনো নিয়ম বা ব্যক্তিগত পছন্দের ক্ষেত্রে কোনো এক্সপায়ারি দেবেন না, বরং সেগুলোকে রিভিউ কিউতে পাঠিয়ে দিন। সফটওয়্যার ভার্সন সংক্রান্ত তথ্যের এক্সপায়ারি সেই প্রজেক্টের রিলিজ ক্যাডেন্স বা রিলিজের সময়ের সাথে সামঞ্জস্য রেখে নির্ধারণ করুন। তথ্যটি লেখার সময় যদি আপনি এর স্থায়িত্ব নিশ্চিত করতে না পারেন, তবে বুঝবেন এটি সময়ের সাথে পরিবর্তিত হয়, ক্ষয় হয় না। সেক্ষেত্রে সেটিকে এক্সপায়ার না করে একটি confirmed_at তারিখ দিন এবং রিভিউ করুন।
সংরক্ষিত কোনো তথ্য ভুল হয়ে গেলে তা কি স্বয়ংক্রিয়ভাবে শনাক্ত করা সম্ভব?
নির্ভরযোগ্যভাবে সম্ভব নয়। স্টোরটির নিজস্ব জগতের বাইরের কোনো কিছুর ওপর নজর নেই, তাই কোনো তথ্য কেন ভুল হলো তা এটি দেখতে পায় না। আর স্টোরটি পুনরায় পড়ার কাজ করলে সেটি কেবল সেই পুরনো টেক্সটই আবার পড়ে। আপনি যা স্বয়ংক্রিয় করতে পারেন তা হলো তথ্যের উপস্থাপন: confirmed_at অনুযায়ী সাজিয়ে সবচেয়ে পুরনো সারিগুলো একজন মানুষের সামনে আনুন, অথবা এমন কোনো এজেন্টের সামনে আনুন যার কাছে কোনো রিপোজিটরি, কনফিগারেশন ফাইল বা মনিটরিং এন্ডপয়েন্ট থেকে বর্তমান অবস্থা যাচাই করার টুল আছে। কিউ স্বয়ংক্রিয় করা লাভজনক, তবে সিদ্ধান্ত গ্রহণ স্বয়ংক্রিয় করার প্রযুক্তি এখনো সেই পর্যায়ে পৌঁছায়নি।
আমি একটি মেমরি ডিলিট করেছি কিন্তু সেটি ফিরে এসেছে। কেন?
সাধারণত এর কারণ হলো দুটি স্টোর থাকা এবং আপনি কেবল একটিতে পরিবর্তন করেছেন। মেমরি টেক্সট এবং এর এম্বেডিং সাধারণত একটি ভেক্টর ডেটাবেসে থাকে, আর SQLite ফাইলে থাকে চেঞ্জ লগ। তাই SQLite ফাইল থেকে সারি ডিলিট করলে অডিট রেকর্ড মুছে যায় কিন্তু মেমরি থেকে যায়। লাইব্রেরি API-এর মাধ্যমে ডিলিট করুন যাতে উভয়ই আপডেট হয়। আরেকটি সাধারণ কারণ হলো রিস্টোর করা, যেখানে ভেক্টর স্টোর এবং SQLite ফাইলের স্ন্যাপশট ভিন্ন ভিন্ন সময়ে নেওয়া হয়েছিল। ফলে রিস্টোর করার সময় এমন সারি ফিরে আসে যা অন্য অংশটি আগেই মুছে ফেলেছিল।
এজেন্ট চলাকালীন হাতে কলমে মেমরি ডেটাবেস এডিট করা কি নিরাপদ?
রিড করা নিরাপদ। রাইট করা কেবল রাইট-অহেড লগিং মোডে নিরাপদ, এবং সেক্ষেত্রেও একবারে একজন রাইটার কাজ করতে পারে। আপনি কোন মোডে আছেন তা দেখতে sqlite3 memory.db "PRAGMA journal_mode;" চালান, এবং wal হলো আপনার কাঙ্ক্ষিত উত্তর। যদি আপনি Error: database is locked দেখেন, তবে অন্য কোনো প্রসেস রাইট লক ধরে রেখেছে। সেক্ষেত্রে sqlite3 -cmd ".timeout 5000" ব্যবহার করে আপনার সেশনকে অপেক্ষা করতে বলুন অথবা প্রথমে এজেন্ট সার্ভিসটি বন্ধ করুন। ভেক্টর স্টোর হাতে এডিট করা ভিন্ন বিষয়: এটি লাইব্রেরির ওপর ছেড়ে দিন, কারণ এম্বেডিং এবং টেক্সটকে একে অপরের সাথে সামঞ্জস্যপূর্ণ থাকতে হয়।