تصور کنید یک خبرنگار زردباو دارید که هرگز اجازه ندارد دروغ بگوید. این دقیقاً همان شرطی است که در پروژه «The Estian Tattler» برای تبدیل دادههای یک بازی به یک روزنامهٔ محلی به کار گرفته شد. با اعمال این محدودیت، یک توسعهدهنده ثابت کرد که عاملهای هوش مصنوعی (AI Agents) قادرند کل چرخهٔ توسعهٔ نرمافزار — از ایدهپردازی تا استقرار — را مدیریت کنند و در عین حال، رویکردی سختگیرانه در زمینهٔ مبنیسازی (Grounding) خروجیهای هوش مصنوعی داشته باشند.
بیشتر محتواهای تولیدشده توسط هوش مصنوعی از توهم (Hallucination) رنج میبرند؛ وضعیتی که در آن مدل برای پر کردن شکافهای دانشی خود، واقعیتها را اختراع میکند. این پروژه با تبدیل فایل ذخیرهٔ یک بازی به یک «پایگاهدادهٔ حقیقت»، این مشکل را حل کرده است. در واقع، تاریخ حدوداً ۳۰۰ روزهٔ یک کلونی در بازی RimWorld (که به شدت با مادهای مختلف تغییر یافته بود) به یک تحریریه تبدیل شده است که در آن هر جمله باید یک «رسید» یا مدرک مستند از دادههای منبع ارائه دهد.
معماری حقیقت
این سامانه بر پایهٔ یک خط لوله (Pipeline) شامل Claude Code (که بر روی مدل Opus 5.5 اجرا میشود)، Sanity و Claude Agent SDK بنا شده است. فرآیند با یک اسکریپت ingest پایتون آغاز میشود که فایلهای ذخیره با پسوند .rws را میخواند و آنها را به فرمت NDJSON تبدیل میکند. این مجموعه داده که نمایانگر تاریخچهٔ «قبیله استیان» (Tribe of Estian) از روز ۱۱۷ تا ۳۰۶ است، در مجموع شامل ۶۳۱ رکورد است: ۳۵۴ داستان (tales)، ۵۳ نامه، ۱۴۹ پیام و ۷۵ مکالمه شنیده شده که ۱۷ کلونیست (ساکن کلونی) در آنها حضور دارند.
برای جلوگیری از توهم، توسعهدهنده یک جداسازی سختگیرانه میان «خبرنگار» و «راستیآزمای» ایجاد کرد:
- خبرنگار: یک عامل Claude است که داستانها را با لحنی تند، کنجکاو و به سبک روزنامههای زرد (Tabloid) مینویسد. این عامل در واقع یک نشست (Session) از Claude Agent SDK است که هیچ ابزار داخلی ندارد، بلکه از سه ابزار MCP روی مجموعه داده استفاده میکند:
search_recordsبرای جستوجو،who_isبرای شناسایی شخصیتها وcheck_draftبرای بررسی پیشنویس. - راستیآزمای: یک برنامهٔ ساده به زبان TypeScript است — و نه یک مدل هوش مصنوعی — که هر ادعا را اعتبارسنجی میکند. این برنامه هر جمله را در برابر رکوردهایی که به آنها ارجاع داده شده، میخواند. اگر جملهای نام یک کلونیست یا عددی را ذکر کند که در رکورد ارجاعشده وجود نداشته باشد، پیشنویس بهطور خودکار رد (Fail) میشود.
قوانین تحریریه
خبرنگار برای اینکه بتواند از فیلتر راستیآزمای عبور کند، باید از یک پرامپت سیستمی بسیار سختگیرانه پیروی کند. هر جمله در متن باید به عنوان یک «ادعا» (Claim) یا یک «حاشیه» (Aside) دستهبندی شود و به صورت یادداشتهای Portable Text ذخیره گردد.
- ادعاها: این جملات باید به یک یا چند شناسهٔ رکورد (Record ID) ارجاع دهند. هر کلونیستی که نامش برده میشود باید در آن رکوردها حضور داشته باشد. هر عددی (چه به صورت رقم و چه به صورت کلمه) باید یا در متن رکورد ارجاعشده باشد، یا مربوط به روزِ کلونی در آن رکورد باشد، و یا تعداد رکوردهای ارجاعشده را نشان دهد.
- حاشیهها: این جملات نمایانگر صدای خودِ روزنامه هستند — مثل تیکهها، پرسشها یا ابراز تعجب. در حاشیهها، نام بردن از هر شخص یا استفاده از هرگونه عدد بهطور مطلق ممنوع است.
- تیترها و لیدها (Deks): این بخشها از همان قوانین سختگیرانهٔ ادعاها پیروی میکنند و در برابر تمام رکوردهایی که در بدنهٔ متن به آنها ارجاع شده، بررسی میشوند.
جزئیات محدودیتهای خبرنگار
پرامپت سیستمی خبرنگار، یک شخصیت (Persona) و محدودیتهای ساختاری خاصی را تحمیل میکند. از مدل خواسته شده مانند یک روزنامهٔ محلی کوچک بنویسد که «تند، گرم و کمی کنجکاو» است. این دستورالعمل صراحتاً اختراع انگیزهها، احساسات یا اتفاقات را ممنوع میکند. اگر رکوردها توضیح ندهند که چرا اتفاقی افتاده است، خبرنگار موظف است در یک «حاشیه» درباره آن ابراز تردید کند، نه اینکه حدس بزند.
داستانها کوتاه نگه داشته شدهاند و طول آنها بین ۱۵۰ تا ۳۵۰ کلمه است. تاریخها باید حتماً در قالب روزهای کلونی فرمت شوند (مثلاً: «در روز ۱۴۲»). برای تضمین دقت، خبرنگار ملزم است پیش از ارسال داستان به ویراستار، از ابزار check_draft برای اصلاح خطاها استفاده کند.
گردش کار تحریریه
این خبرخانه دقیقاً مانند یک نشریه واقعی عمل میکند. یک داستان از ۶ مرحله عبور میکند: گزارش (reporting)، راستیآزمایی (fact-check)، ویراستار (editor)، چاپ (printing)، چاپشده (printed) و رد شده (spiked). چرخهٔ اصلاح در انتقال بین این مراحل اتفاق میافتد. اگر راستیآزمایی شکست بخورد و تعداد پیشنویسها کمتر از سه باشد، متن به مرحلهٔ گزارش بازمیگردد؛ در غیر این صورت، داستان بهطور کلی رد (Spike) میشود.
نظارت انسانی آخرین دروازه است. ویراستاری که از Sanity Studio استفاده میکند، میتواند «نمای رسیدها» (Receipts view) را بررسی کند. در این نما، هر جمله دقیقاً در کنار رکورد منبعی که به آن ارجاع داده است قرار میگیرد. ویراستار میتواند داستان را تأیید کند، آن را با یک یادداشت (که باعث بازنشانی تعداد پیشنویسها میشود) بازگرداند یا قطعه را کاملاً حذف کند.
درسهایی از شکست
این پروژه نشان داد که حتی هوش مصنوعیِ «راستیآزماییشده» نیز میتواند در منطق دچار مشکل شود. تا کنون ۶ شماره منتشر شده است، اما چندین پیشنویس در طول فرآیند تحریریه شکست خوردند:
- خطای فقدان (The Absence Error): در یک پیشنویس اولیه درباره تپهای از توتها، مدل ادعا کرد «تنها درام غذایی ثبت شده، زیادهخواری Grasshopper است» و «رکوردها نشان میدهند هیچکس درباره آن اظهارنظر نکرده است». ویراستار متن را بازگرداند و اشاره کرد که اگرچه AI میتواند ثابت کند یک رکورد چه میگوید، اما نمیتواند ثابت کند که «هیچ چیز دیگری وجود ندارد». در پیشنویس دوم، این ادعاها به پرسش تبدیل شدند.
- خطای مقایسهای (The Comparison Error): داستانی درباره Flubber Flubber (یک کلونیست متوفی با ۱۰ رکورد بازدید از قبر) ادعا کرد که یک بازدیدکننده «بیشتر از هر کسی در رکوردها» به قبر او آمده است. چون AI فقط به برخی از بازدیدها ارجاع داده بود، این ادعا غیرقابل اثبات بود. در بازنویسی، به هر ۱۰ بازدید ارجاع داده شد تا خواننده بتواند آنها را بشمارد.
- خطای منطقی (The Logic Error): پیشنویسی درباره Marcellina Triarius با ۷ ادعا از فیلتر TypeScript عبور کرد اما توسط ویراستار رد شد. مدل ادعا کرده بود آخرین کلام با Aquila Summanus بوده، در حالی که در رکورد مربوطه، در واقع Marcellina گوینده بود. این چالشها یادآور پیچیدگیهای مدیریت دادههای متناقض است، مشابه آنچه در راهکار حل خودکار تضادهای اطلاعاتی در مستندات سازمانی با استفاده از Sanity App SDK بررسی شده بود.
- خطای جنسیتی (The Gender Error): هوش مصنوعی بر اساس نام، یک بازدیدکننده را «او (مونث)» خطاب کرد، در حالی که فایل ذخیره بازی هیچ دادهای درباره جنسیت نداشت. این منجر به یک قانون جدید در پرامپت شد: خبرنگار فقط زمانی میتواند از ضمایر جنسیتی استفاده کند که ابزار
who_isصراحتاً جنسیت را ارائه دهد؛ در غیر این صورت باید از نام کلونیست استفاده کند.
زیرساخت فنی و استقرار
بخش Front-end یک سایت استاتیک با Next.js است که روی GitHub Pages میزبانی میشود. وقتی داستانی چاپ میشود، یک repository_dispatch فعال شده و سایت را مجدداً میسازد. در صفحه اصلی، زیر هر ادعا خط کشیده شده است و با نگه داشتن موس روی آن، رسیدها نمایش داده میشوند (مثلاً اگر خبرنگار به ۳۹ رکورد ارجاع داده باشد، عبارت «۳۹ بار» نمایش داده میشود).
سایر اجزای فنی عبارتند از:
- میز شب (The Night Desk): یک اپلیکیشن App SDK که تابلویی از داستانها بر اساس مرحله و یک «سنجش پوشش» (Coverage meter) ارائه میدهد تا مشخص شود روزنامه کدام کلونیستها را نادیده گرفته است. این بخش از
useWorkflowInstancesبرای تابلو،useWorkflowSessionبرای اجرای باز، وuseQueryبرای شمارش داستانها استفاده میکند. همچنین ازuseCreateDocumentوstartInstanceبرای تابع Pitch (پیشنهاد خبر) بهره میبرد. - استودیو (The Studio): شامل طرحوارهها (کلونی، شخصیت، رکورد و داستان)، نمای رسیدها، اکشن Pitch و پلاگین Workflows است. طرحواره از Portable Text با دو یادداشت
claim(که نیاز به حداقل یک ارجاع به رکورد دارد) وasideاستفاده میکند. - Desk-Runner: یک اسکریپت
desk-runner.tsکه اثرات در انتظار (pending effects) را با یک اجاره ۲۰ دقیقهای بر عهده میگیرد تا نشستهای خبرنگار که چند دقیقه زمان میبرند را مدیریت کند.
غلبه بر موانع ساخت
کل این فرآیند ساخت تقریباً توسط Claude Code در یک جلسه طولانی مدیریت شد. توسعهدهنده فایل ذخیره و اهداف کلی را ارائه داد و AI کدها را نوشت و طرحواره را طراحی کرد. با این حال، چندین باگ فنی رخ داد:
- تداخل نامها: در ابتدا، «Snake Rato» به اشتباه به عنوان «Grasshopper Rato» شمرده شد چون نام خانوادگی مشترک داشتند. سیستم بهروزرسانی شد تا نامها به هر شخصیتی که به آن پاسخ میدهد، نگاشت شوند.
- مشکلات وابستگی: بستههای Workflows نیاز به یک npm override داشتند تا
@sanity/mutateروی نسخه 0.18.2 تثبیت شود. همچنین@sanity/icons v5باعث خطایMISSING_EXPORTدر ساخت شد چون هر آیکون را در یک ماژول جداگانه ارسال میکرد. - کرشهای UI: نمای رسیدها هنگام باز شدن از طریق لینک، ابزار structure را کرش میکرد زیرا فاقد
.id()بود. - عیبیابی محیطی: Claude مشکلات ورود به Sanity dev محلی را با اتصال به iframe استودیو مستقر شده از طریق پروتکل Chrome DevTools دیباگ کرد. همچنین با مشکل بههمریختگی بکاسلشها در shell heredocs مواجه شد و در نهایت برای نوشتن فایلها به ابزار editor تغییر مسیر داد.
- منطق App SDK: تابع Pitch در ابتدا منتظر
startInstanceمیماند و سپس اجرا را باز میکرد که باعث تأخیر میشد؛ این مورد با ایجاد ID در همان ابتدا اصلاح شد. همچنین، رد کردن (Spiking) یک داستان باعث میشد لیدها به اشتباه «روی میز» باقی بمانند که اصلاح شد. در نهایت، پیشنهاد مجدد خبر برای Marcellina با خطای «پیشنویسی از این سند وجود دارد» مواجه شد چون داستان جدید میخواست از ID داستان رد شده استفاده کند.
مشخصات پروژه
برای کسانی که قصد بررسی ساختار را دارند، این پروژه از Sanity Project ID lcvgtfvq با مجموعه داده production استفاده میکند. نسخههای چاپ شده عمومی هستند و برای کوئری زدن به توکن نیاز ندارند. کد منبع در دایرکتوریهای زیر تقسیم شده است:
ingest/: اسکریپتهای پایتون برای تبدیل.rwsبه NDJSON.studio/: طرحواره، نمای رسیدها، اکشن Pitch و پلاگین Workflows.newsroom/: تعاریف گردش کار، خبرنگار و راستیآزمای.nightdesk/: اپلیکیشن App SDK.frontpage/: سایت استاتیک Next.js.
این آزمایش گفتگو را از «چگونه پرامپت بنویسیم» به «چگونه حفاظها (Guardrails) بسازیم» تغییر میدهد. با قرار دادن یک راستیآزمای کد-محور و قطعی (Deterministic) بین مدل زبانی و خواننده، توسعهدهنده سیستمی ساخت که در آن خلاقیت هوش مصنوعی بهطور سختگیرانهای توسط دادههای قابل تأیید محدود شده است.
برای علاقهمندان به پیادهسازی، لاگ کامل ساخت (BUILDLOG.md) و کد منبع در مخزن booyaka101/estian-tattler در گیتهاب در دسترس است.




گفتگو