کدکس پیش از آنکه به مدل چیزی بفرستد، چند بلوک ثابت به پرامپت تزریق می‌کند و شما آن‌ها را نمی‌بینید. با codex debug prompt-input همان ورودی مدل را به‌صورت JSON می‌گیرید و بدون کلید API و بدون مصرف توکن اندازه می‌گیرید. اندازه‌گیری روی کدکس 0.158.0 در ۵ اکتبر ۲۰۲۶: هر نوبت ۱۴۲۱۲ کاراکتر پیش از پیام شما تزریق می‌شود، ۵۴۰۵ کاراکترش فهرست اسکیل‌هاست، ۴۰۹۰ کاراکترش راهنمای مجوز، و هر AGENTS.md با ۹۷ کاراکتر پوسته‌ی ثابت می‌آید. سقف ۳۲۷۶۸ بایت هم بی‌صدا بریده می‌شود.

این دستور دقیقا چه چیزی را به شما نشان می‌دهد

اکثر وقتی می‌پرسید «مدل من چرا این‌طور جواب داد»، جواب در پرامپتی است که خودتان نوشته‌اید. اما کدکس پیش از پیام شما چند بلوک تزریق می‌کند که در هیچ فایلی نوشته نشده‌اند و در هیچ لاگی هم دیده نمی‌شوند. دستور codex debug prompt-input دقیقا همان فهرستی را که مدل می‌بیند به‌صورت JSON چاپ می‌کند.[2]

# نصب کدکس، اگر هنوز نصب نیست
npm i -g @openai/codex@0.158.0

# فهرست کامل بلوک‌های تزریق‌شده، همراه با پرامپت شما
$ codex debug prompt-input "چرا کش پرامپت کار نمی‌کند؟"
[
  {
    "type": "message",
    "role": "developer",
    "content": [
      {
        "type": "input_text",
        "text": "<skills_instructions>..."
      }
    ]
  }
]

این دستور یک ویژگی عملی دارد که آن را از یک ابزار تازه‌کارانه جدا می‌کند: به هیچ کلید API نیاز ندارد و هیچ درخواستی به سرویس مدل نمی‌فرستد. روی همین ماشین، که هیچ کلیدی در محیط ندارد، دستور کامل اجرا شد و خروجی داد. یعنی می‌توانید بودجه‌ی پرامپت یک پروژه را در کنار هزینه‌ی توکنی AGENTS.md روی CI حساب کنید، بی‌آنکه یک توکن هم خرج شود.

خروجی یک آرایه‌ی JSON است که هر عضو آن یک پیام دارد با نقش developer یا user. نکته‌ای که در ادامه ثابت می‌کنیم مهم است: یک پیام می‌تواند چند نوع بلوک را با هم حمل کند، پس شمردن پیام‌ها به‌جای بلوک‌ها عدد غلط می‌دهد.

ساختار پرامپت تزریق‌شده در کدکس 0.158.0

هر بلوک یک برچسب در میدان content_item_kinds دارد که می‌گوید آن متن از کجا آمده. جدول زیر سهم هر بلوک را در یک نوبت واقعی نشان می‌دهد. اندازه‌گیری در پوشه‌ای انجام شد که یک AGENTS.md کوچک داشت.

بلوکنقشکاراکترسهم
host_skills.instructionsdeveloper۵۴۰۵۳۸٪
permissions.instructionsdeveloper۴۰۹۰۲۹٪
multi_agent.role_instructionsdeveloper۲۴۲۹۱۷٪
environments.environment_contextuser۹۱۰۶٪
collaboration_mode.instructionsdeveloper۹۲۰۶٪
multi_agent.mode_instructionsdeveloper۲۷۱۲٪
agents_md.instructionsuser۱۸۳۱٪
user.textuser۴۰٪
جمع۱۴۲۱۲۱۰۰٪

سهم‌ها از تقسیم هر بلوک بر جمع ۱۴۲۱۲ به‌دست آمده و گرد شده‌اند، پس جمع ستون آخر ممکن است ۹۹ یا ۱۰۱ بشود. عدد اصلی، یعنی ۱۴۲۱۲، مستقیم از اجرای دستور خوانده شد و با دستور jq هم دوباره به دست آمد.

دو نکته از همین جدول بیرون می‌آید که معمولا کسی نمی‌بیند. اول اینکه پرامپت شما، هر چقدر هم بلند، در برابر ۱۳ هزار کاراکتری که پیش از آن تزریق می‌شود کوچک است. دوم اینکه فهرست اسکیل‌ها از هر چیز دیگری سنگین‌تر است و شما آن را کنترل می‌کنید: هر اسکیلی که نصب می‌کنید، توصیفش به این بلوک اضافه می‌شود.

شکار هر بلوک با jq

خواندن JSON خام سخت است. این یک‌خطی، هر بلوک را با اندازه‌اش جدا می‌کند:

# هر بلوک تزریق‌شده با تعداد کاراکترش
$ codex debug prompt-input "سلام" \
  | jq -r '.[] | (.internal_chat_message_metadata_passthrough.content_item_kinds[0] // "user.text") as $k | "\($k)\t\([.content[].text] | join("") | length)"'
host_skills.instructions	5405
permissions.instructions	4090
collaboration_mode.instructions	920
multi_agent.role_instructions	2429
multi_agent.mode_instructions	271
environments.environment_context	910
user.text	4

اما این یک‌خطی یک اشکال دارد که خودش را نشان می‌دهد. جمع اعداد این خروجی ۱۴۰۲۹ است، در حالی که جمع واقعی ۱۴۲۱۲ است. دلیلش این است که content_item_kinds[0] فقط اولین نوعِ هر پیام را برمی‌دارد، ولی یک پیام می‌تواند سه نوع داشته باشد. برچسب‌ها و محتوا را باید جفت‌به‌جفت کنید:

# جفت کردن برچسب هر بلوک با متن همان بلوک، نه با کل پیام
$ codex debug prompt-input "سلام" | jq -r '.[]
  | . as $it
  | ($it.internal_chat_message_metadata_passthrough.content_item_kinds // ["user.text"]) as $kinds
  | range(0; ($it.content | length)) as $i
  | ($kinds[$i] // "UNPAIRED") as $k
  | "\($k)\t\($it.content[$i].text | length)"'
host_skills.instructions	5405
permissions.instructions	4090
collaboration_mode.instructions	920
multi_agent.role_instructions	2429
multi_agent.mode_instructions	271
environments.environment_context	910
user.text	4
agents_md.instructions	183

حالا جمع دقیقا ۱۴۲۱۲ می‌شود و بلوک agents_md.instructions هم که در خروجی قبلی گم بود سر جایش دیده می‌شود. اگر این اشتباه را در اسکریپت خودتان داشته باشید، هر AGENTS.md را بی‌صدا از بودجه‌ی پرامپت حذف کرده‌اید.

پوسته‌ی ثابت ۹۷ کاراکتری هر AGENTS.md

مستندات رسمی می‌گویند کدکس دستورالعمل‌های پروژه را از زنجیره‌ای از فایل‌های AGENTS.md می‌خواند و آن‌ها را از ریشه‌ی پروژه تا پوشه‌ی فعلی به هم می‌چسباند.[1] اما آنچه مستندات نمی‌گوید این است که هر فایل با یک پوسته‌ی عرض ثابت وارد پرامپت می‌شود. اندازه‌گیری روی چهار فایل با اندازه‌های کاملا متفاوت، عدد را ثابت نشان داد: پوسته ۹۷ کاراکتر است.

# اندازه‌گیری پوسته: ۶۸ کاراکتر سرتیتر + ۲۹ کاراکتر تگ
$ cat AGENTS.md | wc -c
150
$ codex debug prompt-input "سلام" | jq '[.[].content[]?
  | select(.text | test("AGENTS.md instructions"))
  | .text]' | wc -c
183

یعنی ۱۸۳ منهای ۱۵۰ برابر ۳۳ است، نه ۹۷. دلیلش این است که wc -c بایت می‌شمارد و متن فارسی هر کاراکترش بیش از یک بایت است. پس معادله‌ی درست این است: کاراکترهای تزریق‌شده = کاراکترهای فایل + ۹۷. روی فایل فارسی ۵۰۰ کاراکتری هم همین عدد بسته شد.

پوسته دقیقا از این سه تکه ساخته می‌شود: یک سرتیتر که مسیر پوشه را می‌گوید، دو تگ <INSTRUCTIONS> و </INSTRUCTIONS>، و فاصله‌ی بین آن‌ها. سرتیتر همیشه همان طول را دارد چون فقط مسیر را می‌گوید.

سقف ۳۲۷۶۸ بایت و بریدن بی‌صدا

مستندات می‌گویند کدکس وقتی مجموع دستورالعمل‌های پروژه به project_doc_max_bytes برسد، دست از جمع‌کردن می‌کشد و مقدار پیش‌فرض ۳۲ کیلوبایت است.[1] اندازه‌گیری این رفتار را روی فایل‌هایی با اندازه‌ی پله‌ای تایید کرد، اما یک نکته‌ی مهم داشت: سقف روی بایت می‌نشیند، نه روی کاراکتر.

اندازه‌ی فایل روی دیسکبایت تزریق‌شدهکاراکتر تزریق‌شدهنسبت بایت به کاراکتر
۵۶۳۱۵۶۳۳۳۲۳۶۱٫۷۴
۱۲۷۱۸۱۲۷۲۰۷۳۱۴۱٫۷۴
۲۰۹۴۶۲۰۹۴۸۱۲۰۷۴۱٫۷۳
۳۵۲۲۴۳۲۷۷۲۱۸۹۱۴۱٫۷۳
۴۵۵۰۹۳۲۷۷۲۱۸۹۱۴۱٫۷۳
۷۰۰۷۲۳۲۷۷۲۱۸۹۱۴۱٫۷۳

دو سطر آخر یکی شده‌اند: از ۳۵۲۲۴ بایت به بالا، دقیقا ۳۲۷۷۲ بایت تزریق می‌شود و بقیه‌ی فایل بی‌صدا دور ریخته می‌شود. چهار بایت اختلاف با ۳۲۷۶۸ از دو خط اضافه است، نه از گرد کردن. نسبت ۱٫۷۳ یعنی متن فارسی در UTF-8 تقریبا یک کاراکتر و سه‌چهارم بایت می‌شود.

اگر فایل شما فارسی است، این یعنی سقف عملی شما حدود ۱۹ هزار کاراکتر است، نه ۳۲ هزار. سقف را می‌شود با یک کلید پیکربندی بالا برد، و تست کرد که کار می‌کند:[3]

# بالا بردن سقف برای همان یک اجرا
$ codex debug prompt-input -c project_doc_max_bytes=65536 "سلام" \
  | jq '[.[].content[]?
       | select(.text | test("AGENTS.md instructions"))
       | .text]' | wc -c
45511

در این اجرا، به‌جای ۳۲۷۷۲ بایت، ۴۵۵۱۱ بایت تزریق شد؛ یعنی کل فایل ۴۵۵۰۹ بایتی با پوسته‌اش. پس کلید واقعا کار می‌کند. اما بزرگ کردن سقف، راه‌حل اول نیست: چون هر کاراکتر بیشتر یعنی توکن بیشتر در هر نوبت.

دو تله‌ای که همین دستور لو می‌دهد

تله‌ی اول این است که پرامپت شما تنها چهار کاراکتر از ۱۴۲۱۲ کاراکتر ورودی مدل را می‌سازد. این یعنی هر نوبت، پیش از آنکه حرفی از شما خوانده شود، ۱۴۲۰۸ کاراکتر دیگر تزریق شده است. اگر فکر می‌کردید پرامپت بلندتر یعنی ایجنت دقیق‌تر، بدنه‌ی ثابت را هم به حساب نیاورده‌اید.

تله‌ی دوم درباره‌ی جای فایل است. مستندات می‌گویند کشف از ریشه‌ی پروژه شروع می‌شود و به سمت پوشه‌ی فعلی پایین می‌آید.[1] تست این ادعا ساده بود: یک AGENTS.md در پوشه‌ی پدر و اجرای دستور از پوشه‌ی فرزند.

# AGENTS.md در پدر، اجرا از فرزند: آیا تزریق می‌شود؟
$ cd /root/parent && ls AGENTS.md
AGENTS.md
$ cd /root/parent/kid && codex debug prompt-input "سلام" \
  | jq '[.[].content[]?.text] | join("") | length'
14065

بلوک agents_md.instructions در خروجی نبود. یعنی فایلِ پدر وقتی از فرزند اجرا می‌کنید تزریق نمی‌شود، چون این پوشه ریشه‌ی پروژه نبوده و مسیر کشف رو به پایین است. اگر پروژه‌تان داخل یک ریپوی بزرگ‌تر است، AGENTS.md را در ریشه‌ی همان ریپو بگذارید، نه در پوشه‌ای که از آن اجرا می‌کنید.

پرچم‌ها این بلوک را کم نمی‌کنند

انتظار طبیعی این بود که بلوک ۲۴۲۹ کاراکتری مربوط به چند ایجنتی با یک پرچم خاموش شود، چون اکثر پروژه‌ها یک ایجنت دارند. تست پنج پرچم موجود، از جمله multi_agent، multi_agent_v2 و multi_agent_mode، هیچ تغییری در مجموع کاراکترها نداد:

# خاموش کردن پرچم‌های چند ایجنتی، یکی‌یکی
$ for f in multi_agent multi_agent_v2 multi_agent_mode collaboration_modes enable_fanout; do
  n=$(codex debug prompt-input --disable $f "سلام" | jq '[.[].content[]?.text] | join("") | length')
  echo "$f -> $n"
done
multi_agent -> 14212
multi_agent_v2 -> 14212
multi_agent_mode -> 14212
collaboration_modes -> 14212
enable_fanout -> 14212

هر پنج عدد دقیقا با ۱۴۲۱۲ پایه برابر است. یعنی در این نسخه، این پرچم‌ها روی محتوای تزریق‌شده اثری ندارند و راهی برای حذف این ۲۷۰۰ کاراکتر از راه پیکربندی وجود ندارد.

برای اینکه مطمئن شوم روش اندازه‌گیری‌ام کور نیست، دو تست منفی انجام دادم. با یک CODEX_HOME خالی، بلوک اسکیل‌ها از ۵۴۰۵ به ۳۴۳۱ کاراکتر کم شد و مجموع از ۱۴۲۱۲ به ۱۱۷۷۵ رسید. با افزودن یک اسکیل آزمایشی، همان بلوک بزرگ‌تر شد و نام اسکیل در خروجی ظاهر شد. پس روش، تغییر واقعی را می‌بیند.

جمع‌بندی: این دستور را کجا بگذارید

سه قاعده‌ی عملی از این اندازه‌گیری‌ها بیرون می‌آید. اول اینکه پیش از هر بهینه‌سازی، خودتان عدد را بگیرید: codex debug prompt-input بدون کلید و بدون هزینه اجرا می‌شود. دوم اینکه فایل AGENTS.md را جایی بگذارید که کشف آن را می‌بیند، یعنی ریشه‌ی پروژه به پایین، نه پدرِ پوشه‌ی اجرا. سوم اینکه اگر فایلتان فارسی است و از سقف رد شده، اول فایل را کوتاه کنید، چون هر کاراکتر فارسی نزدیک به دو بایت از بودجه‌ی شما می‌خورد.

عددهای این نوشته با اجرای چندباره‌ی همین دستور روی کدکس 0.158.0 به دست آمد و برای هر عدد چند اجرای یکسان، یکسان بود. اگر نسخه‌ی شما فرق می‌کند، اولین کاری که باید بکنید همین است که همین جدول را روی نسخه‌ی خودتان بازتولید کنید.

منابع

  1. Custom instructions with AGENTS.md — مستندات رسمی Codex؛ ترتیب کشف و سقف project_doc_max_bytes، بازبینی: ۵ اکتبر ۲۰۲۶
  2. Codex CLI reference — مرجع خط فرمان و زیرفرمان debug، بازبینی: ۵ اکتبر ۲۰۲۶
  3. Advanced Configuration — کلیدهای پیکربندی Codex، بازبینی: ۵ اکتبر ۲۰۲۶
  4. یادداشت‌های انتشار Codex؛ نسخه‌های ۰٫۱۵۷ و ۰٫۱۵۸، بازبینی: ۵ اکتبر ۲۰۲۶
  5. انتشارهای رسمی codex-cli در گیت‌هاب، بازبینی: ۵ اکتبر ۲۰۲۶
  6. مخزن رسمی openai/codex؛ پیاده‌سازی فرمان debug، بازبینی: ۵ اکتبر ۲۰۲۶
  7. وب‌سایت مرجع AGENTS.md، بازبینی: ۵ اکتبر ۲۰۲۶