الانتقال إلى المحتوى الرئيسيتخطَّ إلى المحتوى
تحليل معمق

عدة وكلاء برمجة في مستودع واحد: مطالبات بدل الأقفال

يتصادم وكلاء البرمجة المتوازيون عند الدمج لأن أيًا منهم لا يعرف ما يعدّله الآخرون. صار وكلاء Foxl Code السحابيون يطالبون بالملفات التي يوشكون على تغييرها، ويعرفون أيّ وكلائك النشطين الآخرين طالب من قبل بمسار متداخل. تُسجَّل المطالبة في كل الأحوال ولا تقفل شيئًا؛ وعند التعارض يُطلب من الوكيل أن يرسل التغيير إلى صاحب المطالبة، أو ينتظر، أو يسألك. مخرجات حقيقية من الأمر المنشور، وقواعد glob، وتكلفة قاعدة البيانات، وهل يلتزم الوكلاء بالقاعدة فعلًا، وهو ما لا نعرفه بعد.

Foxl Teamتاريخ التحديث قراءة في 9 دقيقة

ملف رُبطت إلى زاويته بطاقتا وكيلين، زرقاء وبرتقالية، وبجانبه قفل مفتوح ملقى: كلتا المطالبتين بالملف مسجلتان، ولا شيء مقفل.
في هذه الصفحة
روجع في 27 أكتوبر 2026 مقارنة بالتنفيذ المنشور والاختبارات المحفوظة.

شغّل عدة وكلاء برمجة في مستودع واحد وسيتصادمون عند الدمج، لأن أيًا منهم لا يعرف ما يعدّله الآخرون. يشغّل Foxl Code وكلاء برمجة مثل Claude Code وCodex في السحابة، ومنذ v0.9.3-beta.5 يُطلب من كل وكيل أن يطالب بالمسارات التي يوشك على تغييرها، بتشغيل foxl-agents claim <paths> قبل أن يبدأ التعديل. فإن كان وكيل آخر للمستخدم نفسه لا يزال يعمل (وكيل نشط) قد طالب من قبل بمسار متداخل، تذكر الإجابة ذلك الوكيل والملاحظة التي تركها. لا يُقفل أي شيء. وعند التعارض يُطلب من الوكيل أن يرسل إلى صاحب المطالبة التغيير الذي يحتاجه، أو ينتظر حتى يحرر صاحبها المطالبة، أو يسأل المستخدم، وتزول مطالباته هو عندما ينتهي تشغيله.

لماذا يتصادم وكلاء البرمجة المتوازيون

يعمل كل وكيل في نسخة العمل الخاصة به وعلى فرعه الخاص، فلا شيء يمنع اثنين منهم من تغيير الملف نفسه. ويُبلغ Git عن تعارض نصي عند دمج طلب الدمج الثاني، بعد أن يكون الوكيلان قد انتهيا ودُفع ثمن عملهما. أما التعارض الدلالي، حيث يجتاز كل من طلبي الدمج فحوصه لكن البناء ينكسر بعد دمجهما معًا، فلا يُبلغ عنه شيء إلى أن يدمجهما شخص معًا. ولا تلتقط المطالبات هذا النوع إلا عندما يلمس الوكيلان المسارات نفسها.

وقد أرانا إعدادنا الخاص ما الذي يساعد. نبني Foxl بـ 15 إلى 20 جلسة Claude Code تعمل بالتوازي على مستودعه، وفي 14 يومًا تبادلت هذه الجلسات 4,773 رسالة. وما منعها من الكتابة فوق عمل بعضها كان قائمة ملكية مشتركة. تسجّل جلسة منسّقة واحدة أي جلسة تملك أي ملفات (وأي أجزاء منها، في حالة ملف تلمسه جلسات كثيرة)، والجلسة التي تحتاج إلى تغيير في ملف لا تملكه ترسل التغيير إلى المالك بدل أن تجريه بنفسها. وقبل القائمة، كانت الجلسات تعدّل الملفات نفسها في الوقت نفسه ولا تكتشف ذلك إلا عند الدمج. وكان للقائمة ثمنها: فمن 123 مرة تعطّلت فيها جلسة خلال تلك الأيام الأربعة عشر، كانت 33 مرة انتظارًا لرد المنسّق.

كان لدى Foxl Code جزء المراسلة من قبل: الأمر foxl-agents الذي يستطيع كل وكيل سحابي استخدامه لعرض وكلاء المستخدم النشطين الآخرين وإرسال رسالة إليهم. وتضيف المطالبات قائمة الملكية دون منسّق: يعلن كل وكيل ما سيلمسه، وتجيب قاعدة البيانات في طلب واحد.

لماذا المطالبات بدل الأقفال؟

لا ينجح القفل إلا إذا التزم به كل وكيل وحرّره كل وكيل. ووكيل البرمجة الذي يتعطل أو تنفد ميزانيته أو يُلغى لا يحرر شيئًا، لذلك يحتاج القفل إلى مهلة، والمهلة الطويلة بما يكفي لتشغيل حقيقي (قد تمتد تشغيلاتنا أكثر من سبع ساعات) تعطّل الجميع طوال تلك المدة. ووكيلان يحتاج كل منهما إلى سطر واحد في ملف الآخر سينتظر أحدهما الآخر إلى الأبد. والوكيل الذي يُرفض طلبه للقفل لا يبقى أمامه إلا أن يتوقف.

تتجاوز المطالبات كل واحدة من هذه المشكلات. فصاحب المطالبة الذي تعطّل لا يعود محسوبًا بمجرد أن يكفّ تشغيله عن أن يكون نشطًا، فلا مهلة يجب انتظار انقضائها. والمطالبة التي تتداخل مع أخرى لا تُرفض، بل تُسجَّل، ويعود التداخل إلى الوكيل الذي سأل، مع معرّف مهمة صاحب المطالبة وعنوانها والمسارات المطالب بها وملاحظتها. والوكيل الذي يصادف تعارضًا يُطلب منه أولًا أن يرسل إلى صاحب المطالبة التغيير الذي يحتاجه، فيجد الوكيلان اللذان يحتاج كل منهما إلى ملفات الآخر ما يفعلانه غير الانتظار، وإن كان لا شيء يمنع كليهما من الانتظار على أي حال. وهذه هي التعليمات التي يتلقاها كل وكيل، حرفيًا:

- Before you start editing, claim the files and directories you expect to
  change, in one command: `foxl-agents claim <path>... --note "<why>"`. A
  directory claims everything under it; `*`, `?` and `**` globs work. Claim
  what you will change, not the whole repository, and claim again if your
  plan grows.
- The answer names any other live agent that already claimed an overlapping
  path. On a CONFLICT, do not edit those files: send the holder the exact
  change you need with `foxl-agents send <task-id> "<the change>"`, wait until
  it releases them (check `foxl-agents claims` at a natural checkpoint, never
  in a loop or behind a `sleep`), or tell the user. A claim is information,
  not a lock.
- `foxl-agents release <path>...` drops a claim you no longer need. Every
  claim you hold is dropped when your run ends.

تُلحق هذه الأسطر بملف التعليمات على مستوى المستخدم الذي تقرؤه كل CLI: ملف CLAUDE.md لـ Claude Code، وملف AGENTS.md لـ Codex وKiro وCursor وHermes وOpenCode. ويجب أن تصل الآلية نفسها إلى ست أدوات CLI مختلفة، ولهذا فإن foxl-agents أمر صدفة لا خادم MCP. فكل CLI للبرمجة تستطيع تشغيل أمر صدفة، وبذلك يعمل سكربت واحد في الست كلها دون إعداد كل منها لتحميل خادم.

ما الذي يشغّله الوكيل وما الذي يراه

فيما يلي مخرجات حقيقية من الأمر المنشور، شغّلها سكربت لا وكيل، لمهمتين تجريبيتين في مستودع تجريبي. المهمة A مهمة Codex تضيف تحديد معدل الطلبات، والمهمة B مهمة Claude Code تضيف ترقيم الصفحات إلى نقطة النهاية الخاصة بالمستخدمين. والخادم وقاعدة بياناته والأمر هي نفسها التي يحصل عليها الوكلاء السحابيون.

مخرجات الطرفية. تطالب المهمة A بالملفين src/api/middleware.ts وsrc/api/routes.ts مع الملاحظة «adding a token bucket per API key»، وتتلقى ردًا بأن أي وكيل نشط آخر لم يطالب بمسار متداخل. ثم تطالب المهمة B بالمجلد src/api/ وتتلقى CONFLICT يذكر المهمة A وعنوانها والملفين وملاحظتها، يليه توجيه بإرسال التغيير إلى صاحب المطالبة، أو انتظار تحريرها، أو إبلاغ المستخدم. وتعرض قائمة المطالبات النشطة مطالبات المهمتين.
الشكل 1. مخرجات ملتقطة من الأمر المنشور مع الخادم المنشور، يعملان محليًا في workerd (بيئة تشغيل Workers من Cloudflare)، 2026-10-23. ومطالبة المهمة B مسجّلة أيضًا.

طالبت المهمة B بـ src/api/ مع شرطة مائلة في آخره، وهي تدل على مجلد، فخزّنه الخادم على أنه src/api/**. ومن دون الشرطة المائلة كان الأمر سيضيف /** بنفسه: فهو يعمل داخل نسخة العمل، المكان الوحيد الذي يستطيع التمييز بين ملف ومجلد. ويعيد الأمر أيضًا كتابة كل مسار نسبةً إلى جذر المستودع، أيًا كان مجلد العمل، ويرفض أي مسار خارج نسخة العمل قبل أن يرسل أي شيء.

يذكر التعارض ملفَي المهمة A وملاحظتها، وتقرر المهمة B بناءً على الملاحظة. فهنا يمكنها أن تترك الـ middleware وشأنه وترسل إلى المهمة A التغيير الوحيد الذي تحتاجه هناك، أو أن تبدأ بمعالج نقطة النهاية نفسه وتعود للتحقق بعد أن تحرر المهمة A مطالبتها.

كيف تعرف ما إذا كان نمطا glob يتداخلان؟

تتداخل مطالبتان عندما يطابق مسار واحد على الأقل كلتيهما. والقواعد صغيرة عن قصد:

  • المسار العادي نمط glob بلا أحرف بدل؛
  • * يطابق داخل جزء واحد من المسار؛
  • ? يطابق حرفًا واحدًا بالضبط؛
  • الجزء الذي يساوي ** تمامًا يطابق أي عدد من الأجزاء الكاملة؛
  • كل حرف آخر يُفهم حرفيًا، لذلك فإن [id].tsx اسم ملف لا فئة أحرف.

وهذا ما تعيده أداة المطابقة المنشورة لبضعة أزواج:

المطالبة Aالمطالبة Bتداخل
src/api/**src/api/users.tsنعم
src/*.tssrc/api/users.tsلا، * يبقى داخل جزء واحد
src/**/*.test.tssrc/api/*.tsنعم، كلاهما يطابق src/api/x.test.ts
docs/**src/**لا
src/app/[id]/page.tsxsrc/app/a/page.tsxلا، [id] يُفهم حرفيًا
**/*.mddocs/guide.mdنعم

لا تُرفض المطالبة أبدًا بسبب التداخل، لكن بعض الأشكال تُرفض:

  • نمط glob مكوّن من أحرف بدل فقط ويتضمن **، مثل ** أو **/*، لأنه سيطالب بالمستودع كله؛
  • جزء المسار ..، أو شرطة مائلة عكسية، أو حرف تحكم؛
  • علامة ! في البداية، لأنها ليست نفيًا هنا ولا ينبغي أن تبدو كذلك.

تحمل المهمة 50 مطالبة على الأكثر، لا يتجاوز طول كل منها 200 حرف، مع ملاحظات يصل طولها إلى 280 حرفًا.

والمقارنة محدودة حتى لا تكلّف مجموعة غير مألوفة من المطالبات الخادمَ كثيرًا من وقت المعالج. فالمطالبتان اللتان تختلف أجزاؤهما الحرفية الأولى (docs/** وsrc/**) تُستبعدان فورًا. وفي غير ذلك يجري الخادم مرورًا مع حفظ النتائج (memoized) على أزواج الأجزاء، وداخل الجزء على أزواج الأحرف. وتتقاسم كل المقارنات في الطلب الواحد ميزانية من 50,000 خطوة، والزوج الذي لا تبلغه الميزانية يُبلَّغ عنه على أنه تداخل محتمل بدل أن يُهمل، فيظل الطلب الذي نفدت ميزانيته ينبّه الوكيل.

متى تنتهي المطالبة

تنتمي المطالبة إلى تشغيل واحد. فعندما ينتهي التشغيل، تُسقط المهمة كل مطالبة أنشأتها، في الخطوة نفسها التي تسجّل انتهاء التشغيل. ويغطي فحصان عند القراءة التشغيلَ الذي لم يبلغ تلك الخطوة قط: تُتجاهل المطالبة التي لم يعد صاحبها نشطًا، وكذلك المطالبة الأقدم من أطول مدة يمكن أن يستغرقها تشغيل، وهي 7 ساعات و20 دقيقة. وللمطالبات مستوى الظهور نفسه الذي للرسائل: لا يرى مطالبات بعضهم إلا الوكلاء النشطون لمستخدم واحد، الذين يعملون عبر تثبيت GitHub App نفسه على المستودع نفسه.

وكيلان وجدول مطالبات واحد. يطالب الوكيل A بملفين ويتلقى ردًا بعدم وجود تداخل. ويطالب الوكيل B بالمجلد الذي يحتويهما، فتُكتب مطالبته، ويتلقى في المقابل تعارضًا يذكر الوكيل A. ثم تكون أمام الوكيل B ثلاثة خيارات: إرسال التغيير إلى الوكيل A، أو انتظار تحرير الوكيل A لمطالبته، أو سؤال الشخص. وعندما ينتهي تشغيل ما، تُزال مطالباته.
الشكل 2. يعود التعارض إلى الوكيل الذي سأل. لا يُرفض شيء بسبب التداخل، ولا ينتظر شيء على قفل.

كم تكلّف على خطة قاعدة بيانات مجانية

تعيش المطالبات في جدول واحد بفهرس واحد على (المستخدم، المستودع، وقت الإنشاء)، ولا شيء يستطلعه دوريًا. فكل قراءة لا تطلب إلا المطالبات التي أُنشئت في آخر 7 ساعات و20 دقيقة، ووقت الإنشاء هو آخر أعمدة الفهرس، فتقع الصفوف الأقدم خارج النطاق الذي تفحصه قاعدة البيانات. والمطالبات القديمة التي لم يحررها أحد تضيف صفًا واحدًا إلى القراءة سواء كان عددها 1,000 أو 2,000؛ وقد قسنا الحالتين.

نقيس هذا لأن Foxl يعمل على الخطة المجانية من Cloudflare، حيث تتقاسم كل قواعد بيانات D1 في الحساب 5 ملايين صف مقروء و100,000 صف مكتوب يوميًا، ويحصل الحساب على 100,000 طلب Worker يوميًا. والأرقام أدناه مأخوذة من D1 يعمل محليًا في workerd، بيئة تشغيل Cloudflare، ولم تُؤكَّد بعد مقابل مقاييس D1 الخاصة في الإنتاج:

الاستدعاءالصفوف المقروءةالصفوف المكتوبة
المطالبة بعدد N من المساراتنحو 2 لكل مطالبة نشطة على المستودع، إضافة إلى نحو 1 لكل مسار وبضع قراءات ثابتة2 لكل مسار
عرض المطالبات1 + 2 لكل مطالبة نشطة0
التحريرنحو 1 لكل مسار1 لكل صف محرَّر
انتهاء تشغيل لم يطالب بشيء00

مع نحو 50 مهمة سحابية يوميًا لكل المستخدمين، واستدعاءين للمطالبة لكل مهمة، يكون المجموع 100 استدعاء يوميًا. وكل استدعاء طلبان إلى Worker، أحدهما على code.foxl.ai والآخر عبر قفزة داخلية إلى الخادم، أي نحو 200 طلب، أو 0.2% من 100,000 طلب في الخطة المجانية. وحتى لو كلّف كل واحد من هذه الاستدعاءات ما يكلّفه استدعاء مستخدم مشغول في أسوأ الأحوال (عشرة وكلاء يحمل كل منهم خمس مطالبات على مستودع واحد)، فسيُقرأ في اليوم نحو 11,000 صف (0.2% من 5 ملايين) ويُكتب نحو 830 (0.8% من 100,000).

هل يلتزم الوكلاء بالقاعدة فعلًا؟

لا نعرف بعد، وهذه المرحلة مبنية لمعرفة ذلك. فجلساتنا المحلية احتاجت إلى منسّق لفرض القاعدة نفسها، لذلك لا نفترض أن ست أدوات CLI مختلفة ستطالب بالملفات قبل تعديلها وتتراجع عند التعارض لمجرد أن تعليماتها تقول ذلك. فكل تعارض يكتب سجل تدقيق (يتخطى كل مثيل للخادم التكرار للمهمة نفسها وأصحاب المطالبات أنفسهم خلال عشر دقائق)، وكذلك كل رسالة يرسلها وكيل إلى آخر. ويظهر في سجل التدقيق ما إذا كان الوكيل قد راسل صاحب المطالبة، ويظهر في جدول المطالبات ما إذا كان قد حرر مطالبته، ويظهر في فرق (diff) طلب الدمج ما إذا كان قد عدّل الملف رغم ذلك.

وتعتمد المرحلة التالية على ما يُظهره ذلك. فإن احترم الوكلاء المطالبات فعلًا، فالخطة أن يعطي الجزء من Foxl Code الذي يوزّع طلبًا واحدًا على عدة وكلاء كل مهمة فرعية قائمة ملفات، وأن يشغّل المهام الفرعية المتداخلة واحدة تلو الأخرى بدل تشغيلها في الوقت نفسه. وحتى 2026-10-23 كان جدول الإنتاج فارغًا، لأن أي مهمة سحابية لم تبدأ منذ نشر الميزة.

ولمن يبني شيئًا مشابهًا، فالخصائص الثلاث التي سنحتفظ بها هي أن المطالبة تُسجَّل دائمًا، وأن التداخل يعود مع صاحب المطالبة وملاحظته، وأن المطالبة لا تدوم إلا ما دام تشغيلها. ويتناول مقالنا عند إطلاق Foxl Code حلقة الوكيل نفسها، التي تعيد مطابقة الحالة مع الهدف بدل أن تثق بكل خطوة (بالإنجليزية).

المراجع وقراءات إضافية

  1. أسعار Cloudflare D1 وحدود الخطة المجانيةالتوثيق
  2. إصدار Foxl v0.9.3-beta.5إصدار