Get started
بازآرایی چرخه حیات ACP
چرخهٔ حیات ACP در حال حاضر کار میکند، اما بخش بیشازحد بزرگی از آن پس از وقوع استنتاج میشود.
پاکسازی فرایند، مالکیت را از روی PIDها، رشتههای فرمان، مسیرهای wrapper
و جدول زندهٔ فرایندها بازسازی میکند. مشاهدهپذیری نشست، مالکیت را
از روی رشتههای کلید نشست بههمراه جستوجوهای ثانویهٔ sessions.list({ spawnedBy }) بازسازی میکند.
این وضعیت اصلاحات محدود را ممکن میسازد، اما نادیدهگرفتن موارد مرزی را نیز آسان میکند:
استفادهٔ مجدد از PID، فرمانهای نقلقولشده، فرایندهای نوهٔ آداپتور، ریشههای وضعیت چند-Gateway،
cancel در برابر close، و مشاهدهپذیری tree در برابر all، همگی به محلهای جداگانهای
برای کشف دوبارهٔ قواعد مالکیت یکسان تبدیل میشوند.
این بازآرایی، مالکیت را به مفهومی درجهیک تبدیل میکند. هدف، ایجاد سطح محصولی جدید برای ACP نیست؛ بلکه ارائهٔ قراردادی داخلی و ایمنتر برای رفتار موجود ACP و ACPX است.
اهداف
- پاکسازی هرگز به فرایندی سیگنال نمیفرستد، مگر اینکه شواهد زندهٔ فعلی با یک اجارهٔ متعلق به OpenClaw مطابقت داشته باشد.
cancel،closeو دروکردن هنگام راهاندازی، مقاصد چرخهٔ حیات متمایزی دارند.sessions_list،sessions_history،sessions_sendو بررسیهای وضعیت از مدل نشستِ متعلق به درخواستکنندهٔ یکسانی استفاده میکنند.- نصبهای چند-Gateway نمیتوانند wrapperهای ACPX یکدیگر را درو کنند.
- رکوردهای قدیمی نشست ACPX در طول مهاجرت همچنان کار میکنند.
- زمان اجرا همچنان تحت مالکیت Plugin باقی میماند؛ هسته جزئیات بستهٔ ACPX را نمیآموزد.
اهداف خارج از محدوده
- جایگزینکردن ACPX یا تغییر سطح عمومی فرمان
/acp. - انتقال رفتار مختص فروشندهٔ آداپتور ACP به هسته.
- ملزمکردن کاربران به پاکسازی دستی وضعیت پیش از ارتقا.
- واداشتن
cancelبه بستن نشستهای قابلاستفادهٔ مجدد ACP.
مدل هدف
هویت نمونهٔ Gateway
هر فرایند Gateway باید یک شناسهٔ پایدار نمونهٔ زمان اجرا داشته باشد:
type GatewayInstanceId = string;این شناسه میتواند هنگام راهاندازی Gateway ایجاد شود و برای طول عمر آن نصب در وضعیت ماندگار شود. این شناسه یک راز امنیتی نیست؛ بلکه تمایزدهندهٔ مالکیتی است که برای جلوگیری از اشتباهگرفتن فرایندهای ACP یک Gateway با فرایندهای Gateway دیگر استفاده میشود.
مالکیت نشست ACP
هر نشست ACP ایجادشده باید فرادادهٔ مالکیت نرمالشده داشته باشد:
type AcpSessionOwner = { sessionKey: string; spawnedBy?: string; parentSessionKey?: string; ownerSessionKey: string; agentId: string; backend: "acpx"; gatewayInstanceId: GatewayInstanceId; createdAt: number;};Gateway باید این فیلدها را در ردیفهای نشست، هرجا که شناختهشدهاند، بازگرداند. فیلترکردن مشاهدهپذیری باید یک بررسی صرف روی فرادادهٔ ردیف باشد:
canSeeSessionRow({ row, requesterSessionKey, visibility, a2aPolicy,});این کار فراخوانیهای ثانویهٔ پنهان sessions.list({ spawnedBy }) را از
بررسیهای مشاهدهپذیری حذف میکند. یک فرزند ACP میانعاملی ایجادشده متعلق به درخواستکننده است، زیرا
ردیف چنین میگوید، نه به این دلیل که یک کوئری دوم اتفاقاً آن را پیدا میکند.
اجارههای فرایند ACPX
هر اجرای wrapper تولیدشده باید یک رکورد اجاره ایجاد کند:
type AcpxProcessLease = { leaseId: string; gatewayInstanceId: GatewayInstanceId; sessionKey: string; wrapperRoot: string; wrapperPath: string; rootPid: number; processGroupId?: number; commandHash: string; startedAt: number; state: "open" | "closing" | "closed" | "lost";};فرایند wrapper شناسهٔ اجاره و شناسهٔ نمونهٔ Gateway را بهصورت آرگومانهای قابلانتقال دریافت میکند:
--openclaw-acpx-lease-id ... --openclaw-gateway-instance-id ...هرگاه پلتفرم اجازه دهد، راستیآزمایی باید فرادادهٔ زندهٔ فرایند را ترجیح دهد که نقلقولگذاری فرمان نتواند موجب اشتباه در آن شود:
- PID ریشه همچنان وجود دارد
- مسیر زندهٔ wrapper زیر
wrapperRootاست - گروه فرایند، در صورت دسترسبودن، با اجاره مطابقت دارد
- آرگومانها شامل شناسهٔ اجارهٔ مورد انتظار هستند
- هش فرمان یا مسیر فایل اجرایی با اجاره مطابقت دارد
اگر فرایند زنده قابل راستیآزمایی نباشد، پاکسازی بهصورت بسته شکست میخورد.
کنترلکنندهٔ چرخهٔ حیات
یک کنترلکنندهٔ چرخهٔ حیات ACPX معرفی کنید که مالک اجارههای فرایند و خطمشی پاکسازی باشد:
interface AcpxLifecycleController { ensureSession(input: AcpRuntimeEnsureInput): Promise<AcpRuntimeHandle>; cancelTurn(handle: AcpRuntimeHandle): Promise<void>; closeSession(input: { handle: AcpRuntimeHandle; discardPersistentState?: boolean; reason?: string; }): Promise<void>; reapStartupOrphans(): Promise<void>; verifyOwnedTree(lease: AcpxProcessLease): Promise<OwnedProcessTree | null>;}cancelTurn فقط لغو نوبت را درخواست میکند. نباید فرایندهای قابلاستفادهٔ مجدد wrapper
یا آداپتور را درو کند.
closeSession مجاز به دروکردن است، اما فقط پس از بارگذاری رکورد نشست،
بارگذاری اجاره و راستیآزمایی اینکه درخت زندهٔ فرایند همچنان متعلق به آن
اجاره است.
reapStartupOrphans از اجارههای باز موجود در وضعیت آغاز میکند. میتواند از جدول فرایند
برای یافتن فرزندان استفاده کند، اما نباید ابتدا فرمانهای دلخواهی را که شبیه ACP هستند
اسکن کند و سپس تصمیم بگیرد که احتمالاً متعلق به ما هستند.
قرارداد Wrapper
wrapperهای تولیدشده باید کوچک باقی بمانند. آنها باید:
- آداپتور را، در صورت پشتیبانی، در یک گروه فرایند آغاز کنند
- سیگنالهای عادی خاتمه را به گروه فرایند ارسال کنند
- مرگ والد را تشخیص دهند
- هنگام مرگ والد، SIGTERM را ارسال کنند، سپس wrapper را زنده نگه دارند تا مسیر جایگزین SIGKILL اجرا شود
- در صورت امکان، PID ریشه و شناسهٔ گروه فرایند را به کنترلکنندهٔ چرخهٔ حیات گزارش دهند
wrapperها نباید دربارهٔ خطمشی نشست تصمیم بگیرند. آنها فقط پاکسازی محلی درخت فرایند را برای گروه آداپتور خود اعمال میکنند.
قرارداد مشاهدهپذیری نشست
مشاهدهپذیری باید از مالکیت نرمالشدهٔ ردیف استفاده کند:
type SessionVisibilityInput = { requesterSessionKey: string; row: { key: string; agentId: string; ownerSessionKey?: string; spawnedBy?: string; parentSessionKey?: string; }; visibility: "self" | "tree" | "agent" | "all"; a2aPolicy: AgentToAgentPolicy;};قواعد:
self: فقط نشست درخواستکننده.tree: نشست درخواستکننده بهعلاوهٔ ردیفهایی که متعلق به درخواستکنندهاند یا از آن ایجاد شدهاند.all: همهٔ ردیفهای همان عامل، ردیفهای میانعاملی مجاز بر اساس a2a و ردیفهای میانعاملی ایجادشدهٔ متعلق به درخواستکننده، حتی زمانی که a2a عمومی غیرفعال است.agent: فقط همان عامل، مگر اینکه یک رابطهٔ مالکیت صریح نشان دهد ردیف متعلق به درخواستکننده است.
این امر tree و all را یکنواخت میکند: all نباید فرزند متعلق به درخواستکنندهای را پنهان کند که
tree نمایش میدهد.
برنامهٔ مهاجرت
مرحلهٔ 1: افزودن هویت و اجارهها
gatewayInstanceIdرا به وضعیت Gateway اضافه کنید.- یک مخزن اجارهٔ ACPX زیر دایرکتوری وضعیت ACPX اضافه کنید.
- پیش از ایجاد یک wrapper تولیدشده، اجاره را بنویسید.
leaseIdرا در رکوردهای جدید نشست ACPX ذخیره کنید.- فیلدهای موجود PID و فرمان را برای رکوردهای قدیمی حفظ کنید.
مرحلهٔ 2: پاکسازی با اولویت اجاره
- پاکسازی بستن را تغییر دهید تا ابتدا
leaseIdرا بارگذاری کند. - پیش از ارسال سیگنال، مالکیت زندهٔ فرایند را در برابر اجاره راستیآزمایی کنید.
- مسیر جایگزین فعلی PID ریشه و ریشهٔ wrapper را فقط برای رکوردهای قدیمی حفظ کنید.
- پس از پاکسازی راستیآزماییشده، اجارهها را
closedعلامتگذاری کنید. - اگر فرایند پیش از پاکسازی از بین رفته است، اجارهها را
lostعلامتگذاری کنید.
مرحلهٔ 3: دروکردن هنگام راهاندازی با اولویت اجاره
- دروکردن هنگام راهاندازی، اجارههای باز را اسکن میکند.
- برای هر اجاره، فرایند ریشه را راستیآزمایی و فرزندان را جمعآوری کنید.
- درختهای راستیآزماییشده را از فرزندان به والدین درو کنید.
- اجارههای قدیمی
closedوlostرا با یک بازهٔ نگهداری محدود منقضی کنید. - اسکن نشانگر فرمان را فقط بهعنوان مسیر جایگزین موقت برای رکوردهای قدیمی حفظ کنید که در صورت امکان با ریشهٔ wrapper و نمونهٔ Gateway محافظت میشود.
مرحلهٔ 4: ردیفهای مالکیت نشست
- فرادادهٔ مالکیت را به ردیفهای نشست Gateway اضافه کنید.
- به نویسندههای ACPX، زیرعامل، وظیفهٔ پسزمینه و مخزن نشست بیاموزید که
ownerSessionKeyیاspawnedByرا مقداردهی کنند. - بررسیهای مشاهدهپذیری نشست را به استفاده از فرادادهٔ ردیف تبدیل کنید.
- جستوجوهای ثانویهٔ
sessions.list({ spawnedBy })در زمان مشاهدهپذیری را حذف کنید.
مرحلهٔ 5: حذف روشهای اکتشافی قدیمی
پس از یک بازهٔ انتشار:
- اتکا به رشتههای ذخیرهشدهٔ فرمان ریشه را برای پاکسازی ACPX غیرقدیمی متوقف کنید
- اسکنهای نشانگر فرمان هنگام راهاندازی را حذف کنید
- جستوجوهای فهرستِ مسیر جایگزین مشاهدهپذیری را حذف کنید
- رفتار دفاعیِ شکست بسته را برای اجارههای مفقود یا غیرقابل راستیآزمایی حفظ کنید
آزمونها
دو مجموعهٔ مبتنی بر جدول اضافه کنید.
شبیهساز چرخهٔ حیات فرایند:
- استفادهٔ مجدد از PID توسط فرایندی نامرتبط
- استفادهٔ مجدد از PID توسط ریشهٔ wrapper متعلق به Gateway دیگر
- فرمان ذخیرهشدهٔ wrapper در پوسته نقلقولگذاری شده است، اما فرمان زندهٔ
psچنین نیست - فرایند فرزند آداپتور خارج میشود، اما فرایند نوه در گروه فرایند باقی میماند
- مسیر جایگزین SIGTERM هنگام مرگ والد به SIGKILL میرسد
- فهرست فرایندها در دسترس نیست
- اجارهٔ منقضی با فرایند مفقود
- فرایند یتیم هنگام راهاندازی با wrapper، فرزند آداپتور و فرایند نوه
ماتریس مشاهدهپذیری نشست:
self،tree،agent،all- a2a فعال و غیرفعال
- ردیف همان عامل
- ردیف میانعاملی
- ردیف ACP میانعاملی ایجادشدهٔ متعلق به درخواستکننده
- درخواستکنندهٔ sandboxشده که به
treeمحدود شده است - عملیات فهرست، تاریخچه، ارسال و وضعیت
ناوردای مهم: یک فرزند ایجادشدهٔ متعلق به درخواستکننده در هر جایی قابل مشاهده است
که مشاهدهپذیری پیکربندیشده، درخت نشست درخواستکننده را شامل شود، و all
نباید از tree توانایی کمتری داشته باشد.
نکات سازگاری
رکوردهای قدیمی نشست ممکن است leaseId نداشته باشند. آنها باید از مسیر
پاکسازی قدیمیِ شکست بسته استفاده کنند:
- وجود یک فرایند ریشهٔ زنده را الزامی کنید
- هنگامی که یک wrapper تولیدشده انتظار میرود، مالکیت ریشهٔ wrapper را الزامی کنید
- برای ریشههای بدون wrapper، تطابق فرمان را الزامی کنید
- هرگز فقط بر اساس فرادادهٔ ذخیرهشده و منقضی PID سیگنال ارسال نکنید
اگر یک رکورد قدیمی قابل راستیآزمایی نیست، آن را به حال خود بگذارید. پاکسازی اجاره هنگام راهاندازی و بازهٔ انتشار بعدی باید در نهایت مسیر جایگزین را بازنشسته کنند.
معیارهای موفقیت
- بستن یک نشست قدیمی یا منقضی ACPX نمیتواند فرایند Gateway دیگری را از بین ببرد.
- مرگ والد باعث نمیشود فرایندهای نوهٔ سرسخت آداپتور همچنان در حال اجرا بمانند.
cancelنوبت فعال را بدون بستن نشستهای قابلاستفادهٔ مجدد لغو میکند.sessions_listمیتواند فرزندان ACP میانعاملی متعلق به درخواستکننده را در هر دو حالتtreeوallنمایش دهد.- پاکسازی هنگام راهاندازی بر اساس اجارهها هدایت میشود، نه اسکنهای گستردهٔ رشتهٔ فرمان.
- آزمونهای متمرکز ماتریس فرایند و مشاهدهپذیری، همهٔ موارد مرزی را پوشش میدهند که پیشتر به اصلاحات موردی در بازبینی نیاز داشتند.