كيف تحدد المهام لوكيل الذكاء الاصطناعي: ميزة واحدة في كل مرة، وقائمة ميزات، ومعايير إنجاز
تُسنَد المهام لوكيل الذكاء الاصطناعي واحدةً تلو الأخرى: يكون في العمل دائماً ميزة واحدة بالضبط، لها سلوك قابل للملاحظة وأمر تحقق قابل للتشغيل، ويسجّل الوكيل كل ما يلاحظه في الطريق في قائمة مؤجلة بدلاً من إصلاحه فوراً. وتُحفظ حالة كل ميزة في قائمة قابلة للقراءة الآلية (مثل feature_list.json)، ولا يستطيع نقل الميزة إلى «منجزة» إلا سكربت التحقق وليس الوكيل نفسه.
لماذا يحاول الوكيل إنجاز كل شيء دفعة واحدة
تطلب من الوكيل تنفيذ التسجيل. بعد ساعتين يحتوي الـ diff على اثني عشر ملفاً معدّلاً وثمانمئة سطر جديد: نموذج المستخدم، والتحقق من البريد، وربط خدمة البريد، وتجزئة كلمات المرور، وإعادة هيكلة الـ middleware، ونقل الاختبارات. كل ذلك مترابط بقوة، ولا يمر أي سيناريو من أوله إلى آخره. والجلسة التالية تفتح هذه الشيفرة فلا تعرف من أين تبدأ.
السبب ليس «نموذجاً سيئاً». لدى الإنسان إحساس بأن «هذا يكفي لليوم» وتعب طبيعي، وهما يحدّان من حجم العمل بنفسيهما. أما الوكيل فلا يملك أياً منهما. أن يضيف «وبالمناسبة سأصلح معالجة الأخطاء» يكلّفه بضعة عشرات من الـ tokens، لكن كلفتها على المشروع حقيقية: ينتشر الانتباه، وتزداد الترابطات، ولا شيء يكتمل.
وهناك حساب بسيط. لنفترض أن C هي السعة المفيدة لسياق الوكيل، وأن k هو عدد المهام التي يحملها في الوقت نفسه. تحصل كل مهمة تقريباً على C/k من قدرة الاستدلال. وحين تنخفض هذه القيمة دون ما تحتاجه مهمة واحدة لتكتمل، لا تكتمل أي مهمة. لذلك فإن الجواب الصحيح عن سؤال كيف تحدد المهام لوكيل الذكاء الاصطناعي لا يبدأ من صياغة الـ prompt، بل من قيد: مهمة واحدة في العمل.
قاعدة الميزة الواحدة (WIP=1)
WIP هو work in progress، أي عدد المهام قيد التنفيذ. وللوكيل يُضبط هذا العدد على واحد: في أي لحظة تكون ميزة واحدة نشطة، ولا تبدأ التالية إلا بعد أن تجتاز الحالية الفحص. تُكتب القاعدة في ملف تعليمات المشروع (AGENTS.md أو CLAUDE.md) كي يراها الوكيل في كل جلسة:
## قواعد العمل
- في أي لحظة توجد في الحالة `active` ميزة واحدة بالضبط.
- لا تبدأ الميزة التالية إلا بعد أن تنتقل الحالية إلى `passing`.
- ممنوع «على الهامش» إعادة الهيكلة أو تغيير الأسماء أو إعادة تنظيم المجلدات
أو إصلاح أخطاء غير مرتبطة. إذا وجدت مشكلة فسجّلها في `BACKLOG.md` وتابع.
- أي تغيير خارج نطاق الميزة النشطة يُعدّ مخالفة، حتى لو كان مفيداً.
البند الأخير أهم مما يبدو. الوكيل يعتبر إضافاته «على الهامش» مفيدة بصدق، وغالباً ما تكون كذلك. لكن التعديل المفيد خارج النطاق يمزج مهمتين في diff واحد، ولم يعد ممكناً التراجع عن إحداهما أو فحصها منفصلة.
BACKLOG.md: أين تُوضع الإضافات «على الهامش»
لا يكفي منع الاندفاع إلى «الإصلاح على الهامش»، بل يحتاج إلى مخرج مشروع. هذا المخرج هو الملف BACKLOG.md: يسجّل الوكيل فيه المشكلة التي لاحظها في سطر أو سطرين، ثم يعود إلى الميزة الحالية. لا تضيع المعلومة، ولا يتوسع النطاق. ومن دون قائمة مؤجلة سيبدأ الوكيل عاجلاً أم آجلاً بالتحايل على قاعدة الميزة الواحدة، لأن «ترك الخلل بصمت» يبدو له أسوأ من المخالفة.
# BACKLOG.md
- [ ] middleware/errors.py: صيغ أخطاء مختلفة في /auth و/orders
- [ ] tests/: تتكرر fixtures المستخدم في ثلاثة ملفات
- [ ] هل يلزم التحقق من البريد عند التسجيل؟ — اسأل
معايير الإنجاز: «منجَز» يعني أمراً
الجانب الثاني من المشكلة أن الوكيل يقرر بنفسه أن المهمة انتهت. عبارات مثل «الشيفرة تبدو سليمة»، و«الدالة موجودة»، و«لا توجد أخطاء ترجمة» كلها إحساسات لا حقائق. يجب أن يكون معيار الإنجاز بالنسبة للوكيل أمراً قابلاً للتشغيل، بنتيجة واضحة: نجح أو لم ينجح.
F01: تسجيل المستخدم
verification: pytest tests/e2e/test_auth.py::test_register_flow
state: active
الفرق بين معيار ضعيف وآخر صارم يظهر جلياً في الأمثلة:
| معيار ضعيف | معيار صارم |
|---|---|
| الشيفرة تُترجم | يمر pytest tests/e2e/test_cart.py -q |
| لا توجد أخطاء صياغية | يعيد curl إلى /cart الرمز 200 |
الدالة addToCart موجودة | بعد إضافة المنتج يعرضه GET /cart مع المجموع الصحيح |
| تبدو صحيحة | يمر npx playwright test cart.spec.ts |
أمر التحقق الجيد حتمي (النتيجة واحدة لنفس الشيفرة)، ويعمل دون تدخل بشري، ويفحص السلوك من الخارج كما سيراه المستخدم أو عميل الـ API. وإذا تعذّر كتابة مثل هذا الأمر فهذه إشارة إلى أن المهمة مصاغة بغموض.
تفكيك المهمة: ما الحجم المناسب للميزة
أكثر سؤال يُطرح بعد «ميزة واحدة في كل مرة» هو: ما الذي يُعدّ ميزة؟ المهمة الواسعة جداً لا تنتهي في جلسة واحدة، والضيقة جداً تغرق في تكلفة التشغيل والسياق والتحقق.
- واسعة جداً: «نفّذ سلة المشتريات». هذا أسبوع عمل وعشرات السيناريوهات.
- بالحجم المناسب: «يستطيع المستخدم إضافة منتج إلى السلة ورؤيته فيها». سلوك واحد قابل للملاحظة، وسيناريو تحقق واحد.
- ضيقة جداً: «أنشئ حقل quantity في نموذج CartItem». هذه خطوة تنفيذ لا سلوك، ولا معنى لفحصها منفصلة.
اختبار سريع للحجم: ميزة واحدة = جلسة واحدة للوكيل = commit واحد ذو معنى = سيناريو end-to-end واحد. إذا لم تتسع الميزة لجلسة فقسّمها أكثر. وإذا تعذّر كتابة سيناريو مستقل لها فهي صغيرة جداً، فادمجها مع جارتها.
مثال على تفكيك مهمة «سلة المشتريات»
يُفكَّك المطلب الواسع إلى وحدات ذرية، لكل منها سلوك واحد وأمر تحقق خاص بها:
- إضافة منتج إلى السلة ورؤيته في القائمة.
- تغيير الكمية: يُعاد حساب المجموع.
- حذف منتج من السلة.
- تبقى سلة الزائر محفوظة بعد تسجيل الدخول.
- لا يمكن إضافة منتج غير متوفر، ويظهر خطأ مفهوم.
الفحص النهائي للتفكيك: لا تحتاج أي وحدة إلى تعديل وحدة أخرى «على الهامش». إذا كان البند الرابع يتطلب إعادة كتابة البند الأول، فالترتيب أو الحدود سيئة الاختيار.
قائمة الميزات: feature_list.json
قاعدة الميزة الواحدة تنظم الترتيب داخل الجلسة. لكن من يتذكر بين الجلسات ما الذي اكتمل؟ ملاحظة في الدردشة مثل «المصادقة والكتالوج منجزان، والسلة شبه جاهزة لكن فيها أخطاء» لا تفيد الجلسة الجديدة شيئاً: ما معنى «شبه»؟ وأي الاختبارات نجحت؟ وما الذي يعيق الدفع؟ لا أحد يعرف. كل جلسة جديدة كسائق جديد لنفس السيارة: إذا بقيت الملاحظات في رأس السائق السابق فالجديد لا يعرف شيئاً.
لذلك تُخرَج الحالة إلى ملف قابل للقراءة الآلية. وفي الدورة يُسمى هذا الملف .agent/feature_list.json:
[
{
"id": "F01",
"behavior": "يسجّل المستخدم بالبريد وكلمة المرور ويدخل مباشرة",
"verification": "pytest tests/e2e/test_auth.py::test_register_flow",
"state": "passing",
"evidence": "a3f9c12: 1 passed in 2.31s"
},
{
"id": "F02",
"behavior": "يضيف المستخدم منتجاً إلى السلة ويراه في القائمة",
"verification": "pytest tests/e2e/test_cart.py::test_add_item -q",
"state": "active"
},
{
"id": "F03",
"behavior": "الدفع عن الطلب بالبطاقة",
"verification": "pytest tests/e2e/test_payment.py -q",
"state": "blocked",
"blocker": "لا توجد مفاتيح اختبار لبوابة الدفع"
}
]
لكل سجل ثلاثة حقول إلزامية: وصف السلوك (القابل للملاحظة من الخارج)، وأمر التحقق (القابل للتشغيل دون إنسان)، والحالة. والحقل الرابع الاختياري هو evidence: hash الـ commit ومخرجات التحقق، وهي دليل على أن الانتقال إلى passing حدث فعلاً.
لماذا JSON وليس قائمة Markdown؟ لأن القراءة لا تقتصر على الوكيل، بل تقرؤه السكربتات أيضاً: المجدول يختار الميزة التالية من not_started، والمُتحقِّق يأخذ أمر الفحص، وتُجمَع تقارير الجلسة تلقائياً، ويعطي توزيع الحالات مؤشرات صحة المشروع. قائمة الميزات هي عمود فقري للهارنس، لا مذكرة تُكتب للتذكير.
أربع حالات وانتقالات مسموحة
| الانتقال | متى |
|---|---|
not_started → active | تُؤخذ الميزة للتنفيذ، ولا توجد ميزة نشطة أخرى |
active → passing | ينتهي سكربت التحقق بالرمز 0 |
active → blocked | عائق خارجي، والسبب مكتوب في blocker |
blocked → active | رُفع العائق |
لا توجد انتقالات أخرى. وتحديداً لا يوجد طريق إلى passing يتجاوز الفحص، ولا توجد حالة «شبه منجزة».
بوابة passing: الوكيل يطلب ولا يعلن
القاعدة المركزية في المخطط كله: الوكيل لا يعدّل حقل state بيده. يستطيع تشغيل الفحص، لكنه لا يعلن النتيجة بنفسه، فهذا من عمل السكربت. والفرق بين «أبلغ الوكيل» و«أكّد السكربت» هو الفرق بين النص والحقيقة. وتُكتب القواعد في ملف التعليمات أيضاً:
## قواعد قائمة الميزات
- الملف: `.agent/feature_list.json`. وهو المصدر الوحيد للحقيقة حول النطاق.
- توجد ميزة واحدة فقط في الحالة `active`.
- أنت لا تعدّل حقل `state` يدوياً. يغيّره `scripts/verify.sh`.
- لنقل ميزة إلى `passing` شغّل `./scripts/verify.sh F02` واحصل على الرمز 0.
- إذا تعطلت الميزة فانقلها إلى `blocked` واكتب `blocker` بالضرورة.
- الميزات الجديدة تُضاف فقط في `not_started` ومعها أمر تحقق.
السكربت نفسه يفحص على مستويين: أولاً تشغيل عام للمشروع (حتى لا تكسر الميزة بقية الأجزاء)، ثم أمر الميزة المحددة. النجاح المحلي مع تراجع عام لا يُعد نجاحاً.
#!/usr/bin/env bash
# الجزء الوحيد الذي يملك صلاحية وضع passing.
set -uo pipefail
FID="$1"
FILE=".agent/feature_list.json"
CMD=$(jq -r --arg id "$FID" '.[] | select(.id==$id) | .verification' "$FILE")
if [ -z "$CMD" ] || [ "$CMD" = "null" ]; then
echo "FAIL: لا يوجد أمر تحقق لـ $FID"; exit 2
fi
echo "== الفحص العام =="
make check || { echo "FAIL: الفحص العام أحمر، ولا يمكن أن تكون $FID passing"; exit 1; }
echo "== فحص الميزة: $CMD =="
if OUT=$(eval "$CMD" 2>&1); then
SHA=$(git rev-parse --short HEAD)
LAST=$(printf '%s\n' "$OUT" | tail -n 1)
TMP=$(mktemp)
jq --arg id "$FID" --arg ev "$SHA: $LAST" \
'map(if .id==$id then .state="passing" | .evidence=$ev else . end)' \
"$FILE" > "$TMP" && mv "$TMP" "$FILE"
echo "PASS: $FID"
else
printf '%s\n' "$OUT"
echo "FAIL: $FID"; exit 1
fi
قاعدة «لا تعدّل state» المكتوبة في النص لا تزال طلباً. ولكي تصبح قيداً يمكن إغلاق الكتابة في الملف آلياً: بفحص في CI يقارن evidence بسجل الـ commits، أو بتقييد صلاحيات الوكيل في أداتك. المبدأ نفسه في كل الهارنس: القاعدة المهمة تُدعَم بآلية.
كيف تقيس أن الوكيل يُنهي المهام
المقياس المغري هو حجم الشيفرة في الجلسة. عملياً يعمل العكس: الـ diff الكبير غالباً يعني أن الوكيل تشتت بين عدة اتجاهات. عُدّ الميزات في حالة passing، لا عدد الأسطر.
مقياس مفيد من الدورة هو VCR (Verified Completion Rate): عدد المهام المتحقق منها مقسوماً على عدد المهام المُفعَّلة. والقاعدة بسيطة: ما دام VCR أقل من واحد لا يجوز تفعيل مهام جديدة. وهذا ضغط عكسي يطبقه الهارنس على الوكيل، ووسيلة لجعل الأعمال غير المكتملة رقماً ظاهراً لا شعوراً غامضاً.
أخطاء شائعة في إسناد المهام للوكيل
- مهمة بلا أمر تحقق. «نفّذ التسجيل» دون سيناريو: سيقرر الوكيل بنفسه أين يتوقف، وسيخطئ في التقدير.
- عدة ميزات في طلب واحد. «نفّذ السلة والدفع» هما مهمتان نشطتان، ولن تكتمل أيٌّ منهما.
- الحالة في الدردشة فقط. التقدم المكتوب في المحادثة يختفي بانتهاء الجلسة. يجب أن يعيش في ملف.
- تقرير ذاتي بدل التحقق. «قال الوكيل إن الاختبارات نجحت» ليس كـ«أعاد السكربت الرمز 0».
- لا توجد قائمة مؤجلة. منع «الإضافات على الهامش» دون مكان يُسجَّل فيه ما لوحظ يجعل الوكيل يتحايل عليه.
متى لا تكفي ميزة واحدة في كل مرة
مخطط «ميزة واحدة - جلسة واحدة - تحقق واحد» يعمل جيداً مع وكيل واحد في دورة واحدة. وحين تكثر المهام وتظهر فروع مستقلة يُوسَّع إلى منظومة من عدة دورات: عقدة للتخطيط، وعقد للتنفيذ، وعقدة للتحقق، وحواف بينها متوازية أو شرطية، مع العودة إلى التنفيذ إذا فشل الفحص. لكن كل عقدة تنفيذ تعمل هناك بالقاعدة نفسها: مهمة واحدة، ومعيار إنجاز قابل للتشغيل، وحالة خارج رأس الوكيل. الرسم البياني لا يلغي انضباط الميزة الواحدة، بل يُبنى فوقه.
وفي المرجع نفسه تحذير مهم: الدورة تحسّن بأمانة المقياس الذي أُعطيته. إذا كان معيار الإنجاز سيئ الاختيار، مثل «التذكرة أُغلقت» بدلاً من «المشكلة حُلّت»، فسيتعلم الوكيل تنفيذ المعيار لا المهمة. لذلك تستحق صياغة السلوك وأمر التحقق وقتاً أكبر مما يبدو.
خلاصة
باختصار، تُسنَد المهام لوكيل الذكاء الاصطناعي واحدةً تلو الأخرى، بسلوك قابل للملاحظة وأمر تحقق قابل للتشغيل، مع قائمة مؤجلة لكل ما يُلاحَظ في الطريق، وقائمة ميزات في ملف يُنقل فيه الحالة إلى «منجز» عبر سكربت لا عبر الوكيل. ويفرض الهارنس هذه الكوابح، فالوكيل لن يفرضها على نفسه. وتُشرح قاعدة WIP=1 وfeature_list.json وverify.sh بالتطبيق العملي في دورة الهارنس عبر مشروع متكامل.
الأسئلة الشائعة
هل يمكن إعطاء الوكيل عدة مهام دفعة واحدة إذا كانت صغيرة؟
يمكن وضعها في قائمة الميزات، لكن يجب أن تكون مهمة واحدة فقط قيد العمل في وقت واحد. يبدأ الوكيل التالية بعد أن تجتاز الحالية الفحص، وبذلك تكتمل كل مهمة.
ماذا أفعل إذا تعذّر كتابة فحص آلي لمهمة ما؟
غالباً تكون المهمة مصاغة كفعل لا كسلوك. أعد صياغتها بما سيراه المستخدم أو عميل الـ API، وللجزء المرئي مناسبة اختبارات e2e في المتصفح.
من يكتب feature_list.json: الإنسان أم الوكيل؟
من الأسهل وضع القائمة الأولية معاً: يقترح الوكيل تفكيكاً ويعدّله الإنسان. بعد ذلك يستطيع الوكيل إضافة ميزات جديدة، لكن فقط في الحالة not_started ومعها أمر تحقق.
ما الفرق بين feature_list.json والمهام في نظام التتبع (tracker)؟
نظام التتبع مصمم للبشر، أما قائمة الميزات فمصممة للقراءة الآلية: لكل سجل أمر تنفيذ وحالة صارمة يغيّرها السكربت. ويمكن مزامنتهما، لكن الوكيل يحتاج إلى الملف داخل المستودع.
ألا تُبطئ قاعدة الميزة الواحدة العمل؟
الجلسات تصبح أقصر، وتزداد نسبة المهام المنجزة فعلاً. الوقت الذي كان يذهب إلى فرز شيفرة نصف منجزة مختلطة من ثلاث ميزات يُوفَّر.
يُشرح هذا الموضوع بالتفصيل مع التطبيق العملي في دورة «Harness Engineering: инженерия надёжности AI-агентов».
- feature_list.json для AI-агента: WIP=1, статусы фич и верификация через verify.sh
- Graph Engineering для AI-агентов: когда одного цикла мало и нужен граф задач