دع العمل يجري وحده، والوكيل لا يتوقف
«أطلق وانسَ — الوكيل لا ينتظر أثناء تشغيل الأمر.»
ألم الاستدعاء المتزامن المُعيق
أداة bash في S02 متزامنة: subprocess.run(..., timeout=120). تشغيل أمر كـ npm install الذي يستغرق 90 ثانية يُجمّد حلقة الوكيل بأكملها 90 ثانية. المستخدم يحدق في الطرفية لا يعلم هل تعطّل البرنامج أم يعمل.
حل s08: منح الوكيل أداة background_run. تعود فوراً بـ task_id، والأمر يعمل في thread منفصل. الوكيل يواصل الحلقة ويعمل في أشياء أخرى؛ حين تنتهي المهمة الخلفية تُضيف النتيجة إلى قائمة الإشعارات.
def run(self, command: str) -> str: task_id = str(uuid.uuid4())[:8] self.tasks[task_id] = {"status":"running", ...} thread = threading.Thread(target=self._execute, args=(task_id, command), daemon=True) thread.start() return f"Background task {task_id} started" # 立即返回
كيف تعود النتائج إلى الوكيل؟
المفتاح queue آمنة للخيوط: thread الخلفية يُضيف إليها عند الانتهاء؛ الخيط الرئيسي يُفرّغها قبل كل استدعاء LLM، ويُضيف إشعارات الاكتمال إلى messages كرسائل بدور user.
def agent_loop(messages): while True: # Drain bg notifications before each LLM call notifs = BG.drain_notifications() if notifs: messages.append({ "role": "user", "content": f"<background-results>{notif_text}</background-results>", }) response = client.messages.create(...) ...
بهذا إن أطلق الوكيل في الجولة N مهمة خلفية، وانتهت تلك المهمة في الجولة N+3، فإن استدعاء LLM التالي يحمل النتيجة تلقائياً — يرى النموذج كتلة <background-results> ويعلم: «انتهت تلك المهمة، أُكمل».
عرض الجدول الزمني
يتيح المكوّن التالي المحاكاة: الخيط الرئيسي يُحقق كل ثانية (يحاكي إيقاع حلقة الوكيل)؛ يمكنك إطلاق مهام خلفية في أي وقت. شاهد كيف يلتقي المسارين عند «نقطة التفريغ».
أي الأوامر يجب إرسالها للخلفية؟
ليس كل أمر يستحق الخلفية. معياران للحكم:
- المدة: ما دون ثوانٍ قليلة، التشغيل المتزامن أبسط ولا حاجة لصيانة queue.
- أهمية الانتظار: إن كانت الخطوة التالية تعتمد مباشرة على النتيجة (مثل
cat file.txtثمgrepمباشرة)، فلا معنى للخلفية — ستنتظر على أي حال.