الوضع الوكيل: من اقتراح إلى تنفيذ
درس 4 من 12
4الوضع الوكيل: من اقتراح إلى تنفيذ
هنا يتوقّف النموذج عن الاقتراح ويبدأ التعديل الفعلي على ملفاتك وتشغيل الأوامر، ومعه تمرين حقيقي شغّلناه، كشف أن «الاختبار» الذي كتبه الوكيل لا يمكن أن يفشل أبدًا.
ماذا تشاهد في الرسم: الوكيل لا يجيب مرة واحدة: يخطّط، يعدّل، يشغّل، يقرأ الناتج، ثم يعيد. ومراجعتك للفرق هي نقطة التحكم الوحيدة.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
في الوضع الوكيل لم يعد الزميل يقترح عليك فقط، بل يأخذ لوحة المفاتيح: يفتح الملفات ويعدّلها ويشغّل الأوامر بنفسه. هذا يوفّر وقتًا كبيرًا، لكنه مثل أن تعطي أحدًا مفاتيح بيتك: تراقب ما يفعل وتراجع كل تغيير قبل أن تعتمده.
الوكيل حلقة: يخطط، ويستدعي أدوات (قراءة ملف، تعديل، تشغيل أمر)، ويقرأ النتيجة، ويكرر. تتحكم فيه بالأذونات وبمراجعة الفرق diff. والتمرين يكشف اختبارًا كتبه الوكيل لا يمكن أن يفشل أبدًا، فتتأكد أن كل اختبار يفشل أولًا حين تكسر الكود.
المتطلبات قبل البداية
الجزء الثالث
محادثة تعمل وتعرف كيف تقرأ اقتراحًا قبل قبوله. الوكيل هو نفس المحادثة لكن بيدين.
مجلد مشروع مفتوح
الوكيل يحتاج مكانًا يكتب فيه. افتح مجلدًا فارغًا بالاسم mini-agent-demo، لا ملفًا واحدًا.
Node مثبّت
نشغّل جافاسكربت من الطرفية. تأكد بـnode -v؛ أي إصدار حديث يكفي.
قدرة على قراءة diff
سطر بـ+ أُضيف وسطر بـ- حُذف. هذه كل القواعد، وهي نقطة تحكّمك الوحيدة.
المفاهيم الأساسية
الإكمال يكتب سطرًا، والمحادثة تعطيك نصًا تنسخه، والوكيل يكتب ملفات على قرصك ويشغّل أوامر. الفرق ليس في الذكاء بل في الصلاحية.
الوكيل لا يجيب مرة واحدة: يخطّط، يعدّل، يشغّل، يقرأ المخرجات، ثم يصحّح نفسه. هذه الحلقة هي كل ما يميّزه، ولهذا يصلح للمهام ذات نتيجة قابلة للقياس.
المراجعة قبل الكتابة (diff) هي اللحظة الوحيدة التي تملك فيها القرار. لو قبلت بلا قراءة فقد سلّمت المشروع لنموذج لا يعرف نيّتك.
كل أمر يشغّله الوكيل يُنفَّذ على جهازك فعلًا: ينشئ ملفات، يثبّت حزمًا، يكتب فوق ما كتبت. لا تُشغّل وكيلًا في مجلد فيه عمل غير محفوظ في git.
كيف يعمل؟
شكل 1، ثلاثة أوضاع، ثلاثة مخرجات مختلفة تمامًا
شكل 2، حلقة الوكيل: لاحظ أن السهم يعود إلى «الخطة» مرتين، عند رفضك وعند فشل الأمر
التمرين: دع الوكيل يبني، ثم اختبر اختباره
الطلب الذي كتبناه للوكيل كان جملة واحدة: «أنشئ مشروع نرد صغير فيه دالة ترجع رقمًا عشوائيًا من ١ إلى عدد الأوجه، مع اختبار وملف README». خلال ثوانٍ أنشأ ثلاثة ملفات. هذا أول ملف، وهو ممتاز:
// dice.js - a single die roll
function roll(sides) {
if (!Number.isInteger(sides) || sides < 1) {
throw new RangeError("sides must be a positive integer");
}
return Math.floor(Math.random() * sides) + 1;
}
module.exports = { roll };Number.isInteger قبل الاستخدام، ورمى RangeError وهو النوع الصحيح للخطأ، واستخدم Math.floor لا Math.round فلا يُنتج صفرًا. هذا كود أفضل من كثير من الكود البشري.ثم أنشأ ملفًا سمّاه «اختبارًا». وهنا المفاجأة:
// dice.test.js - what the agent called a test
const { roll } = require("./dice");
for (let i = 0; i < 5; i++) {
console.log("roll(6) ->", roll(6));
}$ node dice.test.js roll(6) -> 6 roll(6) -> 2 roll(6) -> 4 roll(6) -> 4 roll(6) -> 6 $ echo $? 0
ماذا فعلنا لنكتشف المشكلة
0، أي «نجاح».Math.floor → Math.round) لنرى إن كان «الاختبار» سيلاحظ.roll(6) -> 7 على نرد سداسي، وأعاد 0 كأن شيئًا لم يكن.exit 1.// one character changed: floor -> round - return Math.floor(Math.random() * sides) + 1; + return Math.round(Math.random() * sides) + 1;
$ node dice.test.js roll(6) -> 2 roll(6) -> 2 roll(6) -> 7 <-- a seven, on a six-sided die roll(6) -> 2 roll(6) -> 3 $ echo $? 0 <-- still "success"
هنا الدرس كله في سطر: ملف يُشغَّل بنجاح ليس اختبارًا. الاختبار شيء يستطيع أن يفشل. هذا هو الاختبار الذي كتبناه بأنفسنا:
// dice.spec.js - a test that can actually fail
const assert = require("assert");
const { roll } = require("./dice");
check("every roll(6) is an integer in 1..6", () => {
for (let i = 0; i < 10000; i++) {
const v = roll(6);
assert.ok(Number.isInteger(v), "not an integer: " + v);
assert.ok(v >= 1 && v <= 6, "out of range: " + v);
}
});
check("all six faces appear in 10000 rolls", () => {
const seen = new Set();
for (let i = 0; i < 10000; i++) seen.add(roll(6));
assert.strictEqual(seen.size, 6, "faces seen: " + [...seen].sort().join(","));
});
check("roll(0) throws RangeError", () => {
assert.throws(() => roll(0), RangeError);
});$ node dice.spec.js # healthy dice.js PASS every roll(6) is an integer in 1..6 PASS all six faces appear in 10000 rolls PASS roll(1) is always 1 PASS roll(0) throws RangeError PASS roll(2.5) throws RangeError PASS roll() with no argument throws RangeError 6/6 checks passed $ echo $? 0
$ node dice.spec.js # after floor -> round FAIL every roll(6) is an integer in 1..6 -> out of range: 7 FAIL all six faces appear in 10000 rolls -> faces seen: 1,2,3,4,5,6,7 FAIL roll(1) is always 1 -> Expected values to be strictly equal: 2 !== 1 PASS roll(0) throws RangeError PASS roll(2.5) throws RangeError PASS roll() with no argument throws RangeError 3/6 checks passed $ echo $? 1 <-- a failure you cannot miss
| اختبار الوكيل | اختبار حقيقي | |
|---|---|---|
| يطبع مخرجات | نعم | نعم |
| يتحقّق من قيمة | لا | نعم |
| يفشل عند كسر الكود | لا | نعم |
| رمز الخروج عند الكسر | 0 | 1 |
| صالح في CI | لا، يمرّ دائمًا | نعم |
اعتقاد شائع × الحقيقة
| الاعتقاد | الحقيقة |
|---|---|
| «الوكيل أنشأ اختبارًا، فالكود مغطّى.» | الوكيل أنشأ ملفًا باسم اختبار. التغطية تُقاس بقدرة الملف على الفشل، لا باسمه. |
| «الوكيل شغّل الأمر ونجح، فالمهمة تمّت.» | «نجح» تعني أن رمز الخروج صفر. قد يكون الأمر لم يفحص شيئًا، أو فحص شيئًا آخر. |
| «مراجعة الـdiff إضاعة وقت، أثق بالأداة.» | الثقة غير قابلة للتحقق. المراجعة هي أرخص اختبار موجود: ثانيتان مقابل ساعة تصحيح. |
| «الوكيل أفضل من المحادثة لأنه أحدث.» | هو أقوى في التنفيذ وأضعف في القرار. المهمة التي لا تملك لها مقياسًا تُحلّ بالحوار لا بالتنفيذ. |
| «لو خرّب شيئًا أرجعه بـ Undo.» | الوكيل يلمس عدة ملفات وقد يشغّل أوامر. git هو زرّ التراجع الحقيقي، لا Ctrl+Z. |
جرّب بنفسك
الوكيل أنشأ ملفًا وشغّله فظهر exit 0 وطُبعت خمسة أرقام. ما الذي أثبته هذا؟
رمز الخروج صفر يعني أن البرنامج انتهى بلا استثناء. برنامج يطبع فقط ينتهي بلا استثناء دائمًا، حتى لو طبع 7 على نرد سداسي، وهذا ما رأيناه فعلًا.
أي مهمة من هذه هي الأنسب للوضع الوكيل؟
لأنها متعدّدة الملفات ولها مقياس نجاح واحد وواضح: الاختبارات تمرّ. الأولى والرابعة قرارات، والثانية شرح، وكلها تُحلّ بالمحادثة.
بطاقات مراجعة سريعة
exit 0 يعني؟git commit، لا Ctrl+Z.تحقّق من فهمك
1. بجملة واحدة: ما الفرق بين «الكود يعمل» و«الكود صحيح»؟
«يعمل» يعني أنه انتهى بلا استثناء على المدخل الذي جرّبته. «صحيح» يعني أنه يعطي الناتج المتوقّع على كل المدخلات التي تهمّك، بما فيها الحدّية والخاطئة. الأولى تُلاحظ بالعين، والثانية تحتاج تحقّقًا.
2. لماذا لم يلاحظ اختبار الوكيل أن النرد أعطى 7؟
لأنه لا يقارن شيئًا. console.log يرسل النص إلى الشاشة وينتهي؛ الرقم ٧ ظهر أمام أعيننا ومرّ لأن لا أحد سأل «هل هو ≤ 6؟». الاختبار الحقيقي سأل، فسقط فورًا.
3. (اكتشف الخطأ) وكيل عدّل ٦ ملفات وقال «كل الاختبارات تمرّ». ما أول شيء تفعله؟
تشغّل الاختبارات بنفسك في طرفيتك، ثم تقرأ الـdiff ملفًا ملفًا. السبب: رأيت الآن بعينك أن «تمرّ» قد تعني «لا تفحص». وثاني شيء: git diff لتعرف ما تغيّر فعلًا.
4. وكيل اقترح تثبيت حزمة لم تسمع بها. ماذا تفعل؟
توقف. تسأل عن سببها واسمها الكامل، وتتحقق أنها موجودة ومستخدمة فعلًا. النماذج تخترع أسماء حزم معقولة، وتثبيت حزمة مخترعة أو مشابهة لاسم شهير خطر أمني حقيقي لا مجرّد خطأ برمجي.
5. (قرار) مدير يقول: «شغّلوا الوكيل على المشروع كله وخلّوه ينظّف الكود». ما ردّك؟
أن هذه مهمة بلا مقياس نجاح: «نظّف» ليست نتيجة يمكن لأمر أن يقيسها، فالوكيل سيعدّل كل شيء ولن نعرف ما الذي تحسّن وما الذي انكسر. البديل: فرع جديد، واختبارات تمرّ أولًا، ثم مهمة واحدة محدّدة وقابلة للقياس في كل مرة («أزل الاستيرادات غير المستخدمة ثم شغّل الاختبارات»)، ومراجعة diff بين كل خطوة.