تخطَّ إلى المحتوى

سياسة إعادة المحاولة التلقائية لغير الضغط

يصف هذا المستند مسار إعادة المحاولة القياسي لأخطاء API في AgentSession.

يستثني هذا المستند صراحةً استرداد تجاوز السياق عبر الضغط التلقائي. تتولى منطق الضغط معالجة التجاوز، وهو موثق بشكل منفصل في compaction.md.

حدود النطاق مقابل الضغط

Section titled “حدود النطاق مقابل الضغط”

يتم التحقق من إعادة المحاولة والضغط من مسار agent_end نفسه، غير أنهما مفصولان عن قصد:

  1. يفحص agent_end آخر رسالة من المساعد.
  2. يعمل #isRetryableError(...) أولاً.
  3. إذا بدأت إعادة المحاولة، يُتخطى التحقق من الضغط في تلك الدورة.
  4. تُستثنى أخطاء تجاوز السياق استثناءً صارماً من تصنيف إعادة المحاولة (يختصر isContextOverflow(...) إعادة المحاولة).
  5. يمر التجاوز بعدها إلى #checkCompaction(...) بدلاً من إعادة المحاولة القياسية.

إذن: تستخدم حالات الفشل من نوع الحمل الزائد/معدل الطلبات/الخادم/الشبكة سياسة إعادة المحاولة هذه؛ أما تجاوز نافذة السياق فيستخدم استرداد الضغط.

يتطلب #isRetryableError(...) استيفاء جميع الشروط التالية:

  • يكون stopReason === "error" للمساعد
  • وجود errorMessage
  • ألا تكون الرسالة تجاوزاً للسياق
  • مطابقة errorMessage لـ #isRetryableErrorMessage(...)

مجموعة الأنماط القابلة لإعادة المحاولة الحالية (قائمة على التعبيرات النمطية):

  • overloaded
  • rate limit / usage limit / too many requests
  • فئات الخوادم المشابهة لـ HTTP: 429، 500، 502، 503، 504
  • service unavailable / server error / internal error
  • connection error / fetch failed
  • صياغة retry delay

هذا تصنيف بمطابقة الأنماط النصية، وليس رموز أخطاء الموفر المكتوبة.

دورة حياة إعادة المحاولة وانتقالات الحالة

Section titled “دورة حياة إعادة المحاولة وانتقالات الحالة”

حالة الجلسة المستخدمة في إعادة المحاولة:

  • #retryAttempt: number (0 تعني الخمول)
  • #retryPromise: Promise<void> | undefined (يتتبع دورة حياة إعادة المحاولة الجارية)
  • #retryResolve: (() => void) | undefined (يحل #retryPromise)
  • #retryAbortController: AbortController | undefined (يلغي سكون التراجع)

التدفق (#handleRetryableError):

  1. قراءة مجموعة إعدادات retry.
  2. إذا كان retry.enabled === false، توقف فوراً (false، لم تبدأ إعادة المحاولة).
  3. زيادة #retryAttempt.
  4. إنشاء #retryPromise مرة واحدة (أول محاولة في السلسلة).
  5. إذا تجاوزت المحاولة retry.maxRetries، إصدار حدث الفشل النهائي والتوقف.
  6. حساب التأخير: retry.baseDelayMs * 2^(attempt-1).
  7. لأخطاء حد الاستخدام، تحليل تلميحات إعادة المحاولة واستدعاء تخزين المصادقة (markUsageLimitReached(...)؛ إذا نجح تبديل الموفر/النموذج، يُجبر التأخير على 0.
  8. إصدار auto_retry_start.
  9. إزالة رسالة خطأ المساعد الأخيرة من حالة وقت تشغيل العامل (تُحتفظ بها في سجل الجلسة الدائم).
  10. النوم مع دعم الإلغاء.
  11. عند الاستيقاظ، جدولة agent.continue() عبر setTimeout(..., 0).

ما يُعيد تهيئة عدادات إعادة المحاولة

Section titled “ما يُعيد تهيئة عدادات إعادة المحاولة”

يُعاد تعيين #retryAttempt إلى 0 في هذه الحالات:

  • أول رسالة مساعد ناجحة غير خاطئة وغير ملغاة بعد بدء إعادة المحاولة (تُصدر auto_retry_end { success: true })
  • إلغاء إعادة المحاولة أثناء نوم التراجع
  • مسار تجاوز الحد الأقصى لإعادة المحاولة

يحل #retryPromise ويصفى عند انتهاء سلسلة إعادة المحاولة (نجاح أو إلغاء أو تجاوز الحد الأقصى)، عبر #resolveRetry().

التراجع ودلالات الحد الأقصى للمحاولات

Section titled “التراجع ودلالات الحد الأقصى للمحاولات”

الإعدادات:

  • retry.enabled (الافتراضي true)
  • retry.maxRetries (الافتراضي 3)
  • retry.baseDelayMs (الافتراضي 2000)

ترقيم المحاولات:

  • يُزاد عداد المحاولات قبل التحقق من الحد الأقصى
  • تستخدم أحداث البدء المحاولة الحالية (بترقيم يبدأ من 1)
  • يُبلّغ حدث انتهاء تجاوز الحد الأقصى بـ attempt: this.#retryAttempt - 1 (عدد آخر محاولة إعادة)

تسلسل التراجع مع الإعدادات الافتراضية:

  • المحاولة 1: 2000 مللي ثانية
  • المحاولة 2: 4000 مللي ثانية
  • المحاولة 3: 8000 مللي ثانية

تُستخدم مدخلات تجاوز التأخير فقط في مسار معالجة حد الاستخدام، وفقط للتأثير في قرار تبديل النموذج/الحساب في تخزين المصادقة. في مسار إعادة المحاولة الرئيسي غير المضغوط، يظل التراجع تأخيراً أسياً محلياً ما لم ينجح التبديل (delayMs = 0).

الإلغاء الصريح لإعادة المحاولة

Section titled “الإلغاء الصريح لإعادة المحاولة”

abortRetry():

  • يلغي #retryAbortController (إن وُجد)
  • يحل وعد إعادة المحاولة (#resolveRetry()) لإلغاء حظر المنتظرين

إذا أصاب الإلغاء أثناء النوم، يُصدر مسار الالتقاط:

  • auto_retry_end { success: false, finalError: "Retry cancelled" }
  • يُعيد تعيين المحاولة/وحدة التحكم

تفاعل الإلغاء العام للعملية

Section titled “تفاعل الإلغاء العام للعملية”

يستدعي abort() الدالة abortRetry() قبل إلغاء تدفق العامل النشط. يضمن ذلك إلغاء تراجع إعادة المحاولة عند إصدار المستخدم أمر إلغاء عام.

تفاعل واجهة المستخدم النصية (TUI)

Section titled “تفاعل واجهة المستخدم النصية (TUI)”

عند auto_retry_start، تقوم EventController بـ:

  • تبديل معالج Esc إلى session.abortRetry()
  • عرض نص المحمّل: Retrying (attempt/maxAttempts) in Ns… (esc to cancel)

عند auto_retry_end، تستعيد معالج Esc السابق وتمسح حالة المحمّل.

سلوك البث واكتمال الطلب

Section titled “سلوك البث واكتمال الطلب”

يتوقف prompt() في نهاية المطاف على #waitForRetry() بعد عودة agent.prompt(...).

التأثير:

  • لا يُحسم استدعاء الطلب بالكامل حتى تنتهي أي سلسلة إعادة محاولة مبدوءة (نجاح/فشل/إلغاء)
  • دورة حياة إعادة المحاولة جزء من حدود تنفيذ طلب منطقي واحد

يمنع ذلك المستدعين من معاملة دورة التكرار قيد إعادة المحاولة على أنها مكتملة مبكراً.

محددة في مخطط الإعدادات ضمن مجموعة retry:

  • retry.enabled
  • retry.maxRetries
  • retry.baseDelayMs

مبدّلات برمجية في الجلسة:

  • setAutoRetryEnabled(enabled) تكتب retry.enabled
  • autoRetryEnabled تقرأ retry.enabled
  • isRetrying تُبلّغ عما إذا كانت وعد دورة حياة إعادة المحاولة نشطة

سطح أوامر RPC:

  • set_auto_retrysession.setAutoRetryEnabled(command.enabled)
  • abort_retrysession.abortRetry()

مساعدات العميل:

  • RpcClient.setAutoRetry(enabled)
  • RpcClient.abortRetry()

يُعيد كلا الأمرين استجابات النجاح؛ تأتي تفاصيل تقدم/فشل إعادة المحاولة من أحداث الجلسة المبثوثة، لا من حمولات استجابة الأوامر.

إصدار الأحداث وإظهار الفشل

Section titled “إصدار الأحداث وإظهار الفشل”

أحداث إعادة المحاولة على مستوى الجلسة:

  • auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }
  • auto_retry_end { success, attempt, finalError? }

الانتشار:

  • تُصدر عبر AgentSession.subscribe(...)
  • تُمرَّر إلى مشغّل الامتداد كأحداث امتداد
  • في وضع RPC، تُمرَّر مباشرةً ككائنات أحداث JSON (session.subscribe(event => output(event)))
  • في واجهة المستخدم النصية، تستهلكها EventController لواجهة المحمّل/الخطأ

إظهار الفشل النهائي:

  • عند تجاوز الحد الأقصى أو الإلغاء، auto_retry_end.success === false
  • تعرض واجهة المستخدم النصية: Retry failed after N attempts: <finalError>
  • تستقبل الامتدادات/الخطافات auto_retry_end بالحقول ذاتها
  • يستقبل مستهلكو RPC كائن الحدث ذاته في تدفق stdout

تتوقف إعادة المحاولة ولن تستمر تلقائياً عند حدوث أي مما يلي:

  • retry.enabled خاطئ
  • الخطأ غير مصنف كقابل لإعادة المحاولة
  • الخطأ تجاوز للسياق (مفوض إلى مسار الضغط)
  • تجاوز الحد الأقصى لإعادة المحاولة
  • إلغاء المستخدم لإعادة المحاولة (abort_retry أو Esc أثناء محمّل إعادة المحاولة)
  • يلغي الإلغاء العام (abort) إعادة المحاولة أولاً

لا يزال بإمكان سلسلة إعادة محاولة جديدة البدء لاحقاً عند حدوث خطأ قابل لإعادة المحاولة في المستقبل بعد إعادة تعيين العدادات.

  • التصنيف مطابقة نصية بتعبيرات نمطية؛ لا تُستخدم هنا أخطاء الموفر الهيكلية الخاصة بكل مزوّد.
  • تحذف إعادة المحاولة رسالة خطأ المساعد الفاشلة من سياق وقت التشغيل قبل الاستمرار، لكن سجل الجلسة لا يزال يحتفظ بإدخال الخطأ ذاك.
  • يكشف RpcSessionState حالياً عن autoCompactionEnabled لكن لا يكشف عن حقل autoRetryEnabled؛ يجب على مستدعي RPC تتبع حالة مبدّلهم الخاصة أو الاستعلام عن الإعدادات عبر واجهات API أخرى.