Skip to main content
agent.send(text) أحادي الدورة: كل استدعاء يبدأ بسجل فارغ. أما الجلسة (session) فمحادثة متعددة الدورات: تحتفظ بسجل المحادثة (transcript) وتمرّره إلى النموذج عند كل send().

كائن الجلسة

استدعاءات send() المتزامنة على جلسة واحدة توضع في طابور وتُنفَّذ واحدًا تلو الآخر بترتيب استدعائها، فلا تتداخل الرسائل في سجل المحادثة أبدًا. agent.session({ id, turnPolicy }) يحدد ما يفعله send() أو stream() حين تكون دورة قيد التنفيذ أو تنتظر البدء:
  • 'wait' (الافتراضي): ينتظر ثم يُنفَّذ دورةً مستقلة، كما سبق.
  • 'queue': ينضم مُدخَله إلى تلك الدورة، مثل run.enqueue(): يُضاف بعد نتائج أدوات الخطوة الحالية، فيراه استدعاء النموذج التالي في الدورة. يُنجَز الاستدعاء بنتيجة تلك الدورة (وsignal الخاص به لا يسري على الدورة)، وسجل المحادثة المحفوظ عند انتهاء الدورة يحوي رسالة المستخدم المضافة إلى الطابور في ترتيبها. وstream() المنضم لا يُخرج إلا run.done الخاص بالدورة؛ أما أحداث الدورة، ومنها input.queued وinput.applied، فتُبث على التشغيل الذي بدأها. وإذا انتهت الدورة قبل أن تأخذ المُدخَل (اكتملت، أو توقفت مؤقتًا، أو أُلغيت)، نُفِّذ الاستدعاء دورةً تالية في نهاية المطاف. وإذا فشلت الدورة رُفض الاستدعاء بالخطأ نفسه؛ وفي الجلسة المتينة يبقى المُدخَل في نقطة حفظ الدورة ويطبّقه resume().
  • 'steer': مثل 'queue'، لكن المُدخَل ينضم عبر run.steer(): إذا لم يكن استدعاء النموذج في الدورة قد أصدر شيئًا بعد، أُلغي وأُعيد بالرسالة الجديدة، ولا تُنفَّذ استدعاءات الأدوات التي لم تبدأ في الدورة. وإلا انتظر النقطة الآمنة التالية، كما في 'queue'. يُنجَز الاستدعاء بنتيجة الدورة، وتسري البدائل نفسها.
يقبل send() وstream() نصًا، أو أجزاء محتوى ([{ type: 'text', ... }, { type: 'image', ... }]، وهي رسالة مستخدم واحدة)، أو Message[]؛ وتحتفظ المخازن بالأجزاء، انظر المدخلات متعددة الوسائط. send() الذي يرمي خطأً (خطأ من المزوّد، أو أداة ترمي PropagatingToolError) أو يُلغى عبر signal يترك سجل المحادثة كما كان تمامًا قبل ذلك الاستدعاء. وsend() المُلغى يُنجَز بـ finishReason: 'aborted'، كما يفعل agent.send(). ولا يحوي سجل المحادثة المخزَّن أبدًا دورة للمساعد فيها استدعاءات أدوات دون نتائج الأدوات المقابلة لها. send() الذي يتوقف مؤقتًا عند أداة needsApproval يُنجَز بـ finishReason: 'awaiting-approval'. ثم يتابع agent.approvals.resolve({ id, approved }) التشغيل بوصفه الدورة التالية في الجلسة، فينضم استدعاء الأداة ونتيجته والإجابة النهائية إلى سجل المحادثة (انظر الموافقات). agent.session({ id, limits }) يضبط ميزانيات تشمل دورات الجلسة كلها (مثل { maxCostUsd: 1 })، وتُحسب من الاستهلاك المحفوظ مع سجل المحادثة؛ أما createAgent({ limits }) فيقيّد كل دورة على حدة. انظر الميزانيات.

بث دورة في جلسة

session.stream(input, { signal }) هو agent.stream() لكن لمحادثة: يُرجع AgentRun نفسه (أحداث محددة الأنواع مع وعد result، انظر البث)، غير أن الدورة ترى سجل المحادثة حتى اللحظة وتنضم إليه.
  • يبني قائمة الرسائل نفسها التي يبنيها send()، ويصطف خلف الاستدعاءات السابقة على الجلسة (يمكن الجمع بين send() وstream()).
  • حين ينتهي التشغيل، تُحفظ رسالة المستخدم الجديدة ومخرجات التشغيل تمامًا كما يحفظها send()، وبعد ذلك فقط يُسلَّم run.done. فحين تنتهي حلقة for await، أو يُنجَز run.result، يكون سجل المحادثة مكتملًا. وتحمل الأحداث runId الخاص بالمقبض المُرجَع.
  • التشغيل المُلغى (عبر signal، أو بالخروج من الحلقة مبكرًا) أو الفاشل يترك سجل المحادثة كما كان قبل الاستدعاء، مثل send(). والتشغيل الذي كان قد انتهى فعلًا عند مغادرة الحلقة يُحفظ. والتشغيل الفاشل يُنهي البث بـ error ثم run.done (finishReason: 'error')، ويُرفض run.result. ويشمل ذلك مخزنًا يفشل في التحميل أو الحفظ: وعندها لا يكون للدورة run.start.
  • التشغيل الذي يتوقف مؤقتًا عند أداة needsApproval ينتهي بـ approval.requested ثم run.done ('awaiting-approval')، تمامًا كما يُنجَز send()، ويحوي سجل المحادثة الدورة حتى موضع التوقف. وagent.approvals.resolve() يتابعه بوصفه الدورة التالية في الجلسة. ودالة approve لا تسري على البث، كما في agent.stream().
الجلسات طبقة رقيقة: الجلسة تملك سجل المحادثة وتسلّمه إلى المنفّذ في كل دورة. ومن دون مخزن نقاط حفظ، لا تُحفظ الدورة إلا حين تنتهي، فانهيار العملية في منتصف دورة يضيّعها؛ انظر القسم التالي.

الجلسات المتينة

أعطِ الوكيل store فيه نقاط حفظ (checkpoints)، فتُسجَّل لكل دورة في الجلسة نقطة حفظ بعد كل استجابة من النموذج وكل نتيجة أداة، باستخدام آلية التنفيذ المتين. والدورة التي قطعها انهيار، أو فشل في كتابة نقطة حفظ، أو PropagatingToolError، يمكن عندئذ إتمامها لاحقًا، في عملية أخرى:
  • createAgent({ store }) يقبل أي AgentStore ({ sessions?, checkpoints?, approvals? }، انظر اختيار المخزن): agent.session({ id }) يحفظ سجل محادثته في store.sessions ونقاط حفظه في store.checkpoints، ويحفظ store.approvals توقفات الموافقة. وstore الخاص بالجلسة (وهو SessionStore، أو كائن { sessions, checkpoints }) وcheckpointStore يغلبان مخزن الوكيل، جزءًا جزءًا.
  • agent.resume(id) هو agent.session({ id }).resume()، إلا أنه يُنهي أولًا تشغيلًا بدأ بـ agent.send(message, { sessionId: id }) (انظر التنفيذ المتين).
  • كل دورة تُنفَّذ بـ sessionId: '<session id>.turn-<n>' (وn هو طول سجل المحادثة لحظة بدء الدورة)، فتجد العملية الجديدة الدورة المنقطعة دون أي تتبّع إضافي. والدورة المنتهية تنضم إلى سجل المحادثة، تمامًا كما في send() العادي، وتُحذف نقطة حفظها.
  • resume() يتابع الدورة عبر مسار الاستئناف في المنفّذ (input: []): استدعاءات الأدوات التي سُجّلت نتائجها لا تُنفَّذ من جديد، واستجابة النموذج المسجّلة لا تُطلب من جديد. أما الأداة التي كانت قيد التنفيذ حين ماتت العملية فتُنفَّذ من جديد (مرة واحدة على الأقل، انظر التنفيذ المتين).
  • send() وstream() يستأنفان الدورة المعلّقة أولًا، ثم يرسلان الرسالة الجديدة، فيرى النموذج الدورة بعد إتمامها (وأحداث الدورة المستأنفة لا تُبث). استخدم pending() للتحقق أولًا، أو discardPending() للتخلي عن الدورة غير المنتهية.
  • الدورة التي تتوقف مؤقتًا عند أداة needsApproval تبقى في نقطة حفظها، لا في سجل المحادثة، إلى أن تنتهي. وما دامت تنتظر، يرمي resume() وsend() وstream() الخطأ SessionAwaitingApprovalError (ومعه approvalId)، ويتابع agent.approvals.resolve({ id, approved }) الدورة في هذه الجلسة. بعد إعادة التشغيل، افتح الجلسة واستدعِ resume() (أو send()) مرة واحدة قبل البتّ، ليعرف الوكيل إلى أي جلسة تنتمي الموافقة؛ وأعطِ الوكيلين كليهما store الدائم نفسه (أو approvalStore).
  • الدورة المُلغاة يُتخلى عنها (وتُحذف نقطة حفظها)، كما هو الحال من دون مخزن نقاط حفظ. وclear() يحذف الدورة المعلّقة أيضًا.

المخازن

SessionStore يحفظ سجلات المحادثات بين الاستدعاءات:
  • MemorySessionStore (الافتراضي، مخزن جديد لكل جلسة) يعيش ما عاشت العملية. شارك نسخة واحدة بين الجلسات لتجدها بمعرّفاتها.
  • FileSessionStore(dir) يكتب ملف JSON واحدًا لكل جلسة (<dir>/<id>.json)، كتابةً ذرّية (ملف مؤقت ثم إعادة تسمية)، وينشئ dir عند أول حفظ.
يجب أن تطابق معرّفات الجلسات ^[A-Za-z0-9_-]{1,128}$ (فهي تصير أسماء ملفات، ولذلك يُرفض ../x وa/b بخطأ يوضّح السبب). نفّذ SessionStore بنفسك لحفظ سجلات المحادثات في قاعدة بيانات أو في Redis.

اختيار المخزن

للجلسات ولنقاط حفظ التنفيذ المتين وللموافقات واجهة مخزن لكلٍّ منها (SessionStore وCheckpointStore وApprovalStore). اختر التنفيذ المناسب بحسب المكان الذي تعمل فيه العملية: كل واحد منها جزء من AgentStore: مرّرها معًا هكذا createAgent({ store: { sessions, checkpoints, approvals } }). وmemoryStore() وSqliteStore كائنا AgentStore جاهزان؛ وللملفات العادية، اجمع مخازن الملفات:
وأي كائن له الدوال الثلاث الخاصة بجزء ما يصلح هناك أيضًا: SessionStore على Redis، أو CheckpointStore يعتمد على KV في Cloudflare Workers (والـ Worker المولَّد يستخدم KVCheckpointStore، انظر النشر). SqliteStore يحفظ الثلاثة في ملف قاعدة بيانات واحد، باستخدام node:sqlite المضمّن في Node (دون اعتمادية أصلية (native)؛ يتطلب Node 22.13 أو أحدث، وهو غير مُعاد تصديره من نقطة الدخول الجذرية، فاستيراد SDK لا يحمّله أبدًا):
  • يُنشأ المجلد إن لم يكن موجودًا. والمخطط مُرقَّم الإصدار عبر PRAGMA user_version ويُرحَّل عند الفتح؛ وقاعدة البيانات التي كتبها إصدار أحدث تُرفض.
  • وضع WAL ومهلة انشغال (busy timeout) مدتها 5 ثوانٍ يتيحان لعمليتين تشارك الملف. ولا يبتّ في الموافقة إلا واحدة منهما.
  • نقاط الحفظ ولقطات الموافقة تُخزَّن JSON معتمًا لا يُفسَّر محتواه، فالحقول الجديدة تُكتب وتُقرأ دون تغيير.
  • prune() يزيل الجلسات ونقاط الحفظ التي لم تُحدَّث خلال olderThanMs، والموافقات التي بُتّ فيها قبل تلك المدة؛ أما الموافقات التي لم يُبتّ فيها فتبقى.
  • الملف الذي ليس قاعدة بيانات SQLite يفشل بخطأ يذكر المسار؛ واستخدام المخزن بعد close() يرمي خطأً واضحًا.

تعليمات المشروع

ليست جزءًا من الجلسات، لكنها تُطلب معها كثيرًا: انظر “تعليمات المشروع” في الإعدادات لتعطي الوكيل ملف AGENTS.md الخاص بمستودعك.