سردرگمی حافظه در برابر وضعیت
وقتی یک عامل فراموش میکند که سفارشی قبلاً ثبت شده است، غریزه این است که یک vector store اضافه کنیم. این معمولاً راهحل اشتباهی است، زیرا شکست از جنس یادآوری نیست. از جنس اختیار است.
در بیشتر محصولات، «حافظه» به معنای بازیابی از نوبتهای گذشته است: مکالمه را embed کن، پرسوجوی جدید را embed کن، نزدیکترین تکهها را بیرون بکش، آنها را در پنجره context بچسبان. این مکانیزم واقعاً برای تداوم لحن، برای ارجاعاتی که کاربر سه نشست قبل داده است، و برای حقایق پسزمینهای که به یک تصمیم شکل میدهند، مفید است. این یک فیلتر ارتباط احتمالاتی روی تودهای از متن فقط-افزودنی است.
وضعیت شیء متفاوتی است. وضعیت، سابقه معتبر آن چیزی است که وظیفه به آن متعهد شده است: کدام گامها اجرا شدهاند، کدام اثرات جانبی رخ دادهاند، کدام تعهدات باز ماندهاند، کدام حقایق در حال حاضر درست باور میشوند و بر اساس شواهد چه کسی. وضعیت ناوردا دارد. اگر سفارشی ثبت شده باشد، آن سفارش دقیقاً یک بار وجود دارد، و هیچ مقدار شباهت معنایی این را تغییر نمیدهد.
بازیابی، یادآوری را بهبود میبخشد. نمیتواند ناورداها را تضمین کند. یک retriever میتواند جمله «من سفارش ۴۴۷۱ را ثبت کردم» را بیرون بکشد و به همان اندازه میتواند در بیرون کشیدن آن شکست بخورد، یا جملهای کهنه را بیرون بکشد که ادعا میکند سفارش لغو شده است. سپس عامل بهروانی روی هر تکهای که در مسابقه شباهت برنده شده است، استدلال میکند. هیچ چیز در pipeline نمیتواند ادعا کند که یک تعهد دقیقاً یک بار رخ داده است.
این الگو در تیمها تکرار میشود: یک workflow که گاهی دو بار شارژ میکند، ایمیلی را دوباره میفرستد، یا تیکتی بستهشده را دوباره باز میکند؛ یک جلسه اشکالزدایی که نتیجه میگیرد «عامل فراموش کرد»؛ پیشنهادی برای افزایش کیفیت مدل embedding یا top-k. شکاف واقعی، نبود یک گزارش تراکنش است. از embeddingها خواسته میشود کار یک دفتر کل را انجام دهند، و دفتر کل یک مسئله شباهت نیست.
یک لایه وضعیت چه چیزی را باید تضمین کند
یک لایه وضعیت، یک پایگاه داده با نامی قشنگتر نیست. مجموعهای از تضمینهاست که بقیه سیستم مجاز است آنها را فرض بگیرد. سه مورد بیشترین اهمیت را دارند.
Idempotency. اجرای دوباره یک گام نباید اثرات جانبی آن را تکرار کند. این یعنی هر عمل قابل مشاهده از بیرون، یک کلید پایدار برگرفته از هویت وظیفه و هویت گام حمل میکند، و executor قبل از عمل بررسی میکند که آیا آن کلید قبلاً commit شده است یا نه. بررسی و commit باید نسبت به عمل atomic باشند، یا حداقل بهگونهای ترتیب یابند که یک crash بین آنها قابل بازیابی باشد. بدون این، retryها — که هر runtime عامل مقاوم نیاز دارد — به منبع فساد تبدیل میشوند.
Ordering. عامل باید بداند کدام تعهدات مقدم بر کداماند. برخی وابستگیها علّی هستند (نمیتوانی قبل از تسویه پرداخت ارسال کنی)، برخی صرفاً ترتیبی هستند (نمیتوانی قبل از ایجاد یک log به آن اضافه کنی). ترتیب همان چیزی است که به یک اجرای ازسرگرفتهشده اجازه میدهد تصمیم بگیرد چه کاری هنوز قانونی است. یک کیسه از حقایق بازیابیشده ترتیب ندارد؛ یک state machine دارد.

Recoverability. یک اجرای crash کرده باید از یک checkpoint از سر گرفته شود، نه از ابتدا. این همان تضمینی است که یک demo را از یک system جدا میکند. اگر تنها راه بازیابی، بازپخش کل trajectory باشد، آنگاه هر گام باید idempotent باشد و هر فراخوانی خارجی باید تکرار را تحمل کند — و در عمل یکی از آنها تحمل نخواهد کرد.
این تضمینها رایگان نیستند. یک لایه وضعیت، تأخیر نوشتن، نگهداری schema، و بار migration هر بار که مدل وظیفه تغییر میکند را اضافه میکند. همچنین عامل را محدود میکند: گامهایی که قبلاً آزاد بودند، به عملیاتی تبدیل میشوند که باید ورودیها و اثرات خود را اعلام کنند. این محدودیت همان هدف است، اما هزینه واقعی است، و تیمهایی که آن را سرسری بپذیرند، خود را در حال جنگیدن با schema بهجای مسئله خواهند یافت.
یک schema وضعیت مینیمال
کوچکترین مدل وضعیت مفید، سه نوع اطلاعات را که معمولاً در یک توده context ادغام میشوند، جدا میکند.
Episodic context — آنچه گفته شد. نوبتها، خروجیهای ابزار، اسناد بازیابیشده، خلاصهها. این خانه طبیعی embeddingهاست. حجم بالا، اختیار پایین، و بهطور طراحی lossy است.
Durable facts — آنچه درست است. هر fact نسخهبندی میشود، منبع حمل میکند، و timestamp حمل میکند. یک fact هرگز در جای خود بازنویسی نمیشود؛ یک نسخه جدید جایگزین قدیمی میشود، و قدیمی قابل خواندن باقی میماند. تناقضها تعارضهایی برای حل هستند، نه بهروزرسانیهای خاموش.
Pending obligations — آنچه بدهکار است. وظایف باز، پاسخهای در انتظار، retryهای زمانبندیشده، تعارضهای حلنشده. این مجموعه کاری است که عامل هنگام تصمیمگیری برای گام بعدی به آن مراجعه میکند.
یک شکل مشخص، در pseudo-schema:
facts(id, subject, predicate, value, source, observed_at, superseded_by)
obligations(id, task_id, kind, key, state, created_at, deadline)
episodes(id, task_id, role, content_ref, created_at)
key روی یک obligation همان idempotency key است. superseded_by روی یک fact همان پیوند حل تعارض است. task_id همه چیز را به یک اجرای واحد میبندد.
نیمه دوم طراحی، exposure است. خواندن و نوشتن وضعیت باید ابزارهای صریح باشند، نه اثرات جانبی ضمنی runtime. وقتی عامل state.read_facts(subject) یا state.commit(obligation_id, result) را فراخوانی میکند، آن فراخوانیها در trace در کنار هر فراخوانی ابزار دیگری ظاهر میشوند. این انتقالهای وضعیت را قابل حسابرسی و قابل اشکالزدایی با همان ماشینآلاتی میکند که برای هر چیز دیگری استفاده میشود. همچنین حالت شکست را قابل مشاهده میکند: اجرایی که هرگز یک obligation را commit نمیکند، بهوضو

معاوضه، پرحرفی است. ابزارهای صریح وضعیت، context مصرف میکنند و نوبت اضافه میکنند. تیمهایی که وضعیت را پشت runtime پنهان میکنند، traceهای کوتاهتر و تشخیصهای بدتری میگیرند؛ تیمهایی که آن را آشکار میکنند، به قیمت token میپردازند و در observability سود میکنند. برای وظایف بلندمدت، این معاوضه معمولاً ارزشش را دارد.
موارد آزمونی که شکاف را آشکار میکنند
سه آزمون، یک لایه وضعیت واقعی را از یک لایه بازیابی که لباس وضعیت پوشیده است جدا میکند.
Kill and resume. فرایند را در میانه اجرا متوقف کن، دوباره راهاندازی کن، و بگذار ادامه دهد. اگر نتیجه با یک اجرای بدون وقفه متفاوت باشد — تعداد متفاوت اثرات جانبی، وضعیت نهایی متفاوت، گامی حذف یا تکرار شده — مدل وضعیت کممشخص است. نوشتن این آزمون ارزان است و در آنچه آشکار میکند بیرحم. بیشتر سیستمهایی که آن را پاس میکنند، به این دلیل است که وظیفه بهقدری کوتاه بود که از ابتدا بازپخش شود؛ این همان بازیابی از checkpoint نیست.
همان tool call را دو بار بازپخش کن. یک عمل یکسان با همان idempotency key صادر کن و تأیید کن که دقیقاً یک اثر جانبی وجود دارد. سپس آن را با کلید متفاوتی صادر کن و تأیید کن که دو تا وجود دارد. نیمه دوم مهم است: یک طرح idempotency که همه چیز را فرو میریزد، به همان اندازه اشتباه است که یکی که هیچ چیز را فرو نمیریزد.
یک fact کهنه تزریق کن. یک fact بنویس، آن را supersede کن، سپس به عامل یک context بده که فقط نسخه قدیمی را دارد. عامل باید تعارض را تشخیص دهد و آن را با سابقه معتبر آشتی دهد، نه اینکه بر اساس مقدار کهنه عمل کند. سیستمهای بدون factهای نسخهبندیشده، این آزمون را بیصدا شکست میدهند، زیرا مقدار کهنه دقیقاً مثل یک مقدار تازه به نظر میرسد.
یک آزمون چهارم ارزش افزودن برای تنظیمات چندعاملی دارد: دو عامل بهطور همزمان همان obligation را تلاش میکنند. لایه وضعیت باید آنها را سریال کند، و بازنده باید مشاهده کند که کار قبلاً انجام شده است، نه اینکه ادامه دهد. اینجاست که یک سابقه مشترک و تراکنشی ارزش خود را ثابت میکند؛ یک vector store مشترک هیچ چنین محافظتی ارائه نمیدهد.
جایی که بازیابی هنوز تعلق دارد
هیچکدام از اینها علیه بازیابی استدلال نمیکند. Episodic context واقعاً بزرگ، واقعاً مبهم، و واقعاً مفید است وقتی خلاصه و جستوجو شود. استدلال درباره اختیار است: بازیابی ممکن است به یک تصمیم اطلاع دهد، اما نباید همان چیزی باشد که تصمیم میگیرد آیا یک تعهد قبلاً وجود دارد یا نه.
نتیجهگیری
حافظه در معنای محصولی کنونی، یک مکانیزم یادآوری است. وضعیت یک مکانیزم تعهد است. آنها مسائل متفاوتی را حل میکنند، و درهمآمیختن آنها سیستمهایی تولید میکند که روان، خوب-indexشده، و اشتباه هستند. راهحل بهندرت یک مدل embedding بهتر است. یک گزارش تراکنش است، یک idempotency key، و یک checkpoint — همان ماشینآلات غیرزرقوبرق که به یک عامل اجازه میدهد به کارهایی با پیامد سپرده شود.
