ملفات مواصفات الوكيل (AgentSpec)
الوكيل التصريحي (declarative) ملف YAML (.yaml/.yml) أو JSON (.json)،
تتحقق منه loadSpec() بواسطة zod (src/spec/schema.ts). وهو الصيغة التي
يشغّلها lousho dev وينشرها lousho build.
الحقل الناقص أو غير الصالح يُفشل التحميل بخطأ يسمّي الحقل بعينه، مثل
'prompt': Required.
الأدوات التي يمكن لملف المواصفات الإشارة إليها
الأداتان
github وjira تحتاجان إلى بيانات اعتماد ليس لها حقل في ملف
المواصفات؛ والإشارة إليهما ترمي خطأً يطلب منك بناء الوكيل بـ createAgent()
وتمرير أداة مضبوطة الإعدادات بدلًا من ذلك.
السياسة (policy)
تحوّل specToAgent() الحقل policy إلى خيارات لـ createAgent()، فيفرض ملف
المواصفات ما يصرّح به. الحقول المعروفة تتحقق منها loadSpec()؛ والمفاتيح
الأخرى يُحتفظ بها من أجل مولِّدات بيئات التشغيل (harness) الأخرى (Claude Code
و Codex و Pi)، وهي تقرأ السياسة الخام.
كل حاجز حماية يقبل أيضًا
on: input أو output أو tools (وسائط استدعاء
أداة)، أو قائمة منها. القيمة الافتراضية [input, output]. الاسم أو الخيار غير
المعروف يُفشل التحقق، مع ذكر حواجز الحماية المتاحة واقتراح «هل تقصد» (“did you
mean”):
'policy.guardrails.0': unknown guardrail 'deny-topic' (did you mean 'deny-topics'?).
يوقف requiresApproval التشغيل مؤقتًا (finishReason: 'awaiting-approval')
إلى أن تُستدعى agent.approvals.resolve()؛ وقبل هذا التغيير كان ملف المواصفات
الذي يضبطه يشغّل أدواته دون أن يسأل. ويطبع lousho doctor agent.yaml سطرًا
واحدًا لكل قسم من السياسة.
خوادم MCP (mcpServers)
يصرّح mcpServers بخوادم MCP التي يستخدمها الوكيل، في صورة خريطة من اسم الخادم
(وهو يشكّل نطاق أسماء لأدوات ذلك الخادم) إلى خادم stdio (command، ومعه
اختياريًا args وenv) أو خادم HTTP (url، ومعه اختياريًا headers).
كل مُدخل يضبط واحدًا فقط من command / url؛ وargs/env يخصّان stdio وحده
وheaders يخصّ HTTP وحده. تتحقق loadSpec() من الحقل، والمُدخل غير الصالح
يفشل برسالة فيها اسم المُدخل، مثل
'mcpServers.files': AgentSpec validation failed: missing 'command' (stdio server) or 'url' (HTTP server).
والحقل الاختياري approval (annotations أو always أو never) يحدد أيّ أدوات الخادم تطلب الموافقة؛ انظر الموافقة على أدوات MCP.
ويتحقق lousho doctor من أن كل command لخادم stdio يمكن العثور عليه.
specToAgent() الخوادم إلى createAgent({ mcpServers }) الموصوف في
القسم التالي، فتحصل وكلاء lousho dev وlousho mcp على أدواتها.
ربط خوادم MCP (mcpServers، connectMcp())
يقبل createAgent({ mcpServers }) الخريطة نفسها. تتصل الخوادم عند
await agent.ready() أو، تلقائيًا، عند أول send() / stream()؛ وتُضاف
أدوات كل خادم بالصيغة <server>__<tool> (مثل docs__search). الخادم الذي
يتعذر الاتصال به يُفشل ذلك الاستدعاء، والاستدعاء التالي يحاول من جديد.
تقطع agent.close() الاتصال بها (وتوقف عمليات stdio)؛ وأي استدعاء أداة لاحق
يعيد الاتصال. ودون mcpServers لا تفعل ready() وclose() شيئًا.
connectMcp(servers, options?) ومرّر tools الناتجة عنها بنفسك. فهي تتصل بكل
خادم وتسرد أدواته قبل أن يُنجَز وعدها، فتكون الأدوات معروفة سلفًا.
الخيارات:
onError: القيمة'throw'(الافتراضية) ترفض الوعد حين يتعذر الاتصال بخادم، بعد إغلاق الخوادم الأخرى؛ والقيمة'skip'تستبعد ذلك الخادم وتحذّر عبرlogger.lazy(الافتراضيtrue): بعدclose()أو انقطاع الاتصال، يعيد استدعاء الأداة التالي الاتصال. ومعfalseيفشل ذلك الاستدعاء بدلًا من ذلك. سرد الأدوات يحتاج إلى اتصال، ولذلك لا يؤخّرlazyالاتصال الأول أبدًا.logger: يتلقى تحذيرات الخوادم المتخطّاة والأدوات المتخطّاة (الافتراضي: لا شيء).
{ tools, close(), status() }؛ وstatus() تقرن كل خادم بإحدى القيم
'idle' أو 'connected' أو 'failed'.
command وargs؛ ويُضاف env إلى البيئة الافتراضية
(PATH وما شابه) ولا يحل محلها. أما خوادم HTTP فتستخدم النقل streamable HTTP
مع headers في كل طلب. والحزمة @modelcontextprotocol/sdk اعتمادية نظيرة
اختيارية: ثبّتها لاستخدام MCP.
الموافقة على أدوات MCP (approval)
تصف خوادم MCP كل أداة بتوصيفات (annotations) هي readOnlyHint
وdestructiveHint وidempotentHint وopenWorldHint وtitle. وهي مجرد
تلميحات، لكن الـ SDK يتخذها أساسًا للسلوك الافتراضي في
الموافقات: الأداة التي لها readOnlyHint: true تُنفَّذ؛ والأداة
التي لها destructiveHint: true، أو التي لا ترسل destructiveHint (الافتراضي
في مواصفة MCP أنها مُتلِفة)، توقف التشغيل مؤقتًا إلى أن يوافق إنسان؛ و
destructiveHint: false تُنفَّذ. وعليه فالأداة التي بلا توصيفات تطلب
الموافقة. تبقى التوصيفات الخام في descriptor.metadata.mcp.annotations،
ويصبح title هو displayName.
اضبط approval في مُدخل الخادم (mcpServers، createAgent، connectMcp()) أو
في loadMcpTools(client, name, { approval }):
'annotations'(الافتراضي): كما سبق.'always'/'never': اطلب الموافقة لكل الأدوات / لا تطلبها لأيٍّ منها.- دالة
({ name, annotations }) => booleanتقرر لكل أداة على حدة (nameهو اسم الأداة المجرّد؛ وannotationsتكون{}إذا لم يرسل الخادم شيئًا). متاحة في الشيفرة فقط؛ أما ملف المواصفات فيقبل السلاسل النصية الثلاث.
allow أو deny أو ask.
أدوات MCP (Model Context Protocol)
يمكن أيضًا تحميل أدواتClient اتصلت به بنفسك يدويًا - اتصل بـ Client من @modelcontextprotocol/sdk بنفسك وحمّل
أدواته بـ loadMcpTools()، ثم مرّر الناتج إلى createAgent() (أو سجّله في
ToolRegistry):
loadMcpTools متاحة كذلك من جذر الحزمة ومن
@lousho/build-ai-agent/tools.
تقديم وكيل عبر MCP
الدالةserveMcp() هي عكس loadMcpTools(): تعرض وكيلًا (ومعه، اختياريًا،
بعض أدواته) في صورة خادم MCP، فيتمكن Claude Code و Cursor وغيرهما من عملاء MCP
من استدعائه.
- بلا حالة. كل استدعاء لأداة الوكيل محادثة جديدة.
- الإلغاء. إلغاء طلب MCP يُلغي تشغيل الوكيل
(
agent.send(message, { signal })). - الأخطاء. فشل الوكيل يعود في صورة نتيجة MCP فيها
isError: true. - الموافقات. الأدوات المشروطة بموافقة لا يمكن الموافقة عليها عبر MCP.
التشغيل الذي يتوقف مؤقتًا بانتظار موافقة يُرجع
isError: trueمع رسالة تقول ذلك. والأدوات الموسومة بـneedsApprovalلا تُعرَض مباشرة إلا إذا مرّرتallowApprovalTools: true؛ وإن فعلت، شغّلها العملاء دون أي رقابة بشرية. - stdio. لا يُكتب إلى stdout شيء سوى بروتوكول MCP؛ والتحذيرات تذهب إلى stderr.
- HTTP. يرتبط بالعنوان
127.0.0.1افتراضيًا. أضفauth: { type: 'bearer', token }لاشتراط الترويسةAuthorization: Bearer؛ والارتباط بمضيف غير محلي (non-loopback) دونauthيسجّل تحذيرًا.
التوصيفات
يحملtools/list توصيفات MCP ليعرف العملاء ما تفعله الأداة. الأداة التي تحتاج
إلى موافقة يُعلَن عنها بـ readOnlyHint: false, destructiveHint: true؛ وحدّد
التلميحات بنفسك عبر annotations (تُرسل كما هي حرفيًا). الأداة التي بلا
تلميحات لا ترسل شيئًا، فيظل وكيل Lousho الذي يستهلك هذا الخادم يسأل قبل
تشغيلها (انظر approval في connectMcp())؛ وreadOnlyHint: true تُنفَّذ دون
سؤال. والأداة التي تحتاج إلى موافقة لا يُعلَن عنها أبدًا أنها للقراءة فقط.
الأدوات المضمَّنة read_file وlist_dir وglob وgrep وtodo_read
وcurrent_date وday_name للقراءة فقط.
lousho mcp ملف مواصفات وكيل (عبر stdio افتراضيًا):
.mcp.json في
Claude Code):
بيانات اعتماد المزوّدين
المزوّدون الحقيقيون تحدّدهمresolveProvider('<provider>/<model>') (وتُستخدم
أيضًا لملفات المواصفات)، وهي تقرأ بيانات الاعتماد من متغيرات البيئة:
المزوّد
mock لا يحتاج إلى بيانات اعتماد ويُرجع ردودًا معدّة سلفًا؛ وهو ما
تستخدمه الأمثلة ودليل البدء السريع افتراضيًا.
إعادة المحاولة والبديل الاحتياطي لدى المزوّد
تعيدcreateAgent() محاولة استدعاءات النموذج الفاشلة من تلقاء نفسها، ويمكنها
اللجوء إلى نماذج أخرى كبديل احتياطي:
- يقبل
retryخياراتwithRetry()المذكورة أدناه. وهو يسري على السلسلة النصيةmodel(أو النموذج المختار من البيئة) وعلى كل مُدخل فيfallbackModels. تبني createAgent هؤلاء المزوّدين مع تعطيل إعادة المحاولة الخاصة بـ SDK الـai(maxRetries: 0)، فتجري إعادة المحاولة في مكان واحد، ويُجري الافتراضي{ maxRetries: 2 }العدد نفسه من الاستدعاءات كما في السابق. - نسخة
providerالتي تمرّرها تحتفظ بسلوكها الخاص في إعادة المحاولة؛ ولا تُغلَّف بـwithRetry()إلا إذا ضبطتretry. وتعملfallbackModelsمعها أيضًا. - قيم
fallbackModelsسلاسل نصية بالصيغةprovider/model، تُحدَّد مثلmodelعند إنشاء الوكيل. يشغّل الوكيلwithFallback([withRetry(primary), withRetry(fallback1), ...]): كل استدعاء يبدأ بالنموذج الأساسي. - تُبلغ
stream()وsession.stream()عن كل إعادة محاولة بحدثprovider.retryوعن كل انتقال بحدثprovider.fallback(انظر البث). أماsend()فتُرجع النتيجة النهائية كما في السابق.
withRetry(provider, options) وwithFallback(providers, options) أي
LLMProvider وتُرجعان مزوّدًا آخر، فيمكن تركيبهما معًا وتمريرهما في أي موضع
يُقبل فيه مزوّد:
- تعيد
withRetryمحاولةgenerate()وstream()(الافتراضيmaxRetries: 2) عند تجاوز حدود المعدّل (rate limits)، وانقضاء المهلة، وأخطاء الشبكة، واستجابات 5xx، بالتصنيف نفسه الذي تستخدمهcompactProviderError(). لا تُعاد المحاولة عند فشل المصادقة، ولا الطلبات غير الصالحة، ولا أخطاء طول السياق؛ ولا عند الإلغاء. وتلميحretryAfterMsمن المزوّد (الترويسةRetry-After) يحل محل مدة التراجع (backoff). مرّرretryOn(error, attempt)لتغيير القاعدة، وtimeoutMsلتحديد مهلة لكل محاولة، وsignalلإيقاف إعادة المحاولة. - لا تُعاد محاولة استدعاء
stream()إلا إذا رُفض وعده. والخطأ الذي يقع داخل بث أُرجع بالفعل لا تُعاد محاولته. - تجرّب
withFallbackالمزوّدين واحدًا تلو الآخر بالترتيب، وترمي الخطأ الأخير من جديد إذا فشلوا جميعًا. افتراضيًا تنتقل إلى البديل الاحتياطي عند أي خطأ ما عدا الإلغاء (ويغيّرfallbackOnذلك). كل بديل احتياطي يعمل بقيمةdefaultModelالخاصة به. وnameوdefaultModelيعكسان المزوّد الذي خدم آخر استدعاء. - تطبّق
resilientProvider(provider, { maxRetries, timeout })حقلَيLLMProviderConfigاللذين يحملان الاسمين نفسيهما. - المزوّدون المضمَّنون يمرّرون قيمة
maxRetriesمن إعداداتهم (الافتراضي 2) إلى إعادة المحاولة الداخلية في SDK الـai، وهي تجري داخل كل محاولة من محاولاتwithRetry. أنشئ المزوّد الذي تغلّفه معmaxRetries: 0(new OpenAIProvider({ apiKey, maxRetries: 0 })) لتجري إعادة المحاولة في مكان واحد. وتفعلcreateAgent()ذلك للنماذج التي تحدّدها هي.
خيارات createAgent()
إذا لم يُحدَّد
model ولا provider، تحدّد createAgent() المزوّد من البيئة:
LOUSHO_MODEL (سلسلة نصية بالصيغة 'provider/model') إن كان مضبوطًا، وإلا
فأول مزوّد ضُبط متغيره، وتُفحص المتغيرات بهذا الترتيب:
OPENAI_API_KEY (openai/gpt-4o-mini)، ثم ANTHROPIC_API_KEY
(anthropic/claude-3-5-sonnet-latest)، ثم OPENROUTER_API_KEY
(openrouter/openai/gpt-4o-mini)، ثم OLLAMA_BASE_URL (ollama/llama3). وإذا
لم يكن أيٌّ منها مضبوطًا رمت خطأً يسرد بدقة الخيارات أو المتغيرات التي تحل
المشكلة.
أخطاء سوء الإعداد تبيّن طريقة إصلاحها: المفتاح الناقص يسمّي المتغير
(createAgent: OPENAI_API_KEY is not set. ...)، والبادئة غير المعروفة تسرد
البادئات المدعومة وتقترح أقربها، والاعتمادية النظيرة الاختيارية الناقصة تطبع
أمر npm install بنصّه.
الميزانيات
يضعlimits سقفًا لما يجوز للتشغيل أن ينفقه. اضبطه في createAgent() (لكل
تشغيلات الوكيل) أو في AgentExecutor.execute() / stream():
تُفحص الحدود قبل كل استدعاء للنموذج (أي بعد كل دفعة أدوات) وبعد استدعاء
النموذج الذي يطلب أدوات؛ كما يُلغي
maxDurationMs استدعاء النموذج أو الأداة
الجاري عبر إشارة التشغيل. يُعدّ الحد متجاوَزًا حالما يبلغه التشغيل. واستهلاك
الوكلاء الفرعيين يُحتسب من ميزانية وكيلهم الرئيسي: يُضاف حين يعود الوكيل
الفرعي، فيتوقف الوكيل الرئيسي قبل استدعائه التالي للنموذج. والتشغيل الذي ينتهي
من تلقاء نفسه في الخطوة التي بلغت حدًّا يحتفظ بسبب انتهائه هو، كما في
maxSteps.
حين يُتجاوَز حد، يتوقف التشغيل بـ finishReason: 'budget-exceeded' مع
result.budget ({ limit, value, max, scope }). استدعاءات الأدوات التي طلبها
النموذج في تلك الخطوة تحصل على نتيجة «أُلغي» (“cancelled”)، فيبقى سجل المحادثة
صالحًا، ومع مخزن نقاط حفظ يُحفظ في نقطة حفظ على أنه منتهٍ، مثل 'max-steps'.
تُصدر stream() الحدث budget.exceeded قبل run.done. ومع
onExceeded: 'throw' يُرفَض وعد التشغيل بالخطأ BudgetExceededError
(LOUSHO_BUDGET_EXCEEDED، ومعه قيمة budget نفسها) بدلًا من ذلك.
createAgent({ limits }) على كل تشغيل على
حدة: كل send()، وكل دورة في جلسة، يبدأ من الصفر. ويضيف
agent.session({ id, limits }) حدودًا تشمل كل دورات الجلسة: تتوقف الدورة
حالما يبلغ ما أنفقته الجلسة، في دوراتها كلها، حدًّا (وعندها تكون
budget.scope مساوية 'session')، والدورات اللاحقة تتوقف قبل استدعاء النموذج.
ما أنفقته الدورات (الرموز والتكلفة والخطوات وزمن التشغيل) يُحفظ مع سجل
المحادثة، في metadata.sessionUsage على آخر رسالة فيه، فالجلسة التي تُستكمل
من مخزنها تحتفظ بميزانيتها. يسري النوعان معًا؛ وأول حد يُبلَغ يوقف الدورة.
والدورة التي أُلغيت لا تُحتسب.
تعليمات المشروع
كثير من المستودعات يحفظ إرشادات لوكلاء البرمجة في ملفAGENTS.md (أو
CLAUDE.md). يستطيع createAgent إلحاقه بتعليمات الوكيل:
## Project instructions (from AGENTS.md). ويُقرأ مرة واحدة، عند إنشاء الوكيل.
يصعد البحث من cwd (الافتراضي process.cwd()) ويستخدم أقرب مجلد فيه أحد
ملفات files (الافتراضي ['AGENTS.md', 'CLAUDE.md']، وأول تطابق يفوز)، ويتوقف
عند أقرب مجلد يحتوي .git أو عند جذر نظام الملفات. المحتوى الذي يزيد على
32,000 حرف يُقتطع مع علامة تدل على الاقتطاع. وإذا لم يُعثر على ملف لم يُضَف
شيء.
هذه الميزة اختيارية (opt-in) عن قصد: قراءة الملفات من القرص افتراضيًا
ستفاجئ من يضمّنون الـ SDK في خادم، حيث مجلد العمل ليس المشروع الذي يُعنى به
الوكيل. وللعثور على الملف بنفسك (لعرضه مثلًا)، استخدم
loadProjectInstructions({ cwd, files, stopAt, maxChars })، وهي تُرجع
{ path, content } أو undefined.
خيارات AgentExecutor.execute()
الصنف AgentExecutor ساكن (static): استدعِ AgentExecutor.execute(options).
المطلوب فقط agent وinput وprovider. الخيارات الشائعة الاستخدام:
يُنجَز وعدها بكائن
ExecutionResult: { text, messages, toolCalls, usage, finishReason, steps, approvalId? }.
CLI
الأوامرlousho dev وlousho build وlousho mcp وسائر الأوامر، مع
راياتها (flags)، موصوفة في CLI.