Get started
بازآرایی چرخهٔ حیات ACP
چرخه عمر ACP در حال حاضر کار میکند، اما بخش زیادی از آن پس از وقوع، استنباط میشود.
پاکسازی فرایند مالکیت را از PIDها، رشتههای فرمان، مسیرهای wrapper
و جدول زنده فرایند بازسازی میکند. نمایانی نشست مالکیت را
از رشتههای کلید نشست بهعلاوه جستوجوهای ثانویه sessions.list({ spawnedBy }) بازسازی میکند.
این کار اصلاحهای محدود را ممکن میکند، اما باعث میشود موارد مرزی نیز بهراحتی نادیده گرفته شوند:
استفاده دوباره از PID، فرمانهای نقلقولشده، نوههای adapter، ریشههای وضعیت چند 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. - منتقل کردن رفتار adapter ویژه فروشنده 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 یا adapter قابل استفاده دوباره را جمعآوری کند.
closeSession اجازه جمعآوری دارد، اما فقط پس از بارگذاری رکورد نشست،
بارگذاری اجاره، و راستیآزمایی اینکه درخت فرایند زنده هنوز متعلق به همان
اجاره است.
reapStartupOrphans از اجارههای باز در وضعیت شروع میکند. ممکن است از جدول فرایند
برای یافتن فرزندان استفاده کند، اما نباید ابتدا فرمانهای دلخواه شبیه ACP را
اسکن کند و سپس تصمیم بگیرد که احتمالا مال ما هستند.
قرارداد Wrapper
wrapperهای تولیدشده باید کوچک بمانند. آنها باید:
- adapter را در یک گروه فرایند، هرجا پشتیبانی میشود، شروع کنند
- سیگنالهای خاتمه عادی را به گروه فرایند ارسال کنند
- مرگ والد را تشخیص دهند
- هنگام مرگ والد، SIGTERM ارسال کنند، سپس wrapper را زنده نگه دارند تا جایگزین SIGKILL اجرا شود
- وقتی در دسترس است، PID ریشه و شناسه گروه فرایند را به کنترلکننده چرخه عمر گزارش کنند
wrapperها نباید درباره سیاست نشست تصمیم بگیرند. آنها فقط پاکسازی درخت فرایند محلی را برای گروه adapter خودشان اعمال میکنند.
قرارداد نمایانی نشست
نمایانی باید از مالکیت نرمالشده ردیف استفاده کند:
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 نشان میدهد.
برنامه مهاجرت
مرحله ۱: افزودن هویت و اجارهها
gatewayInstanceIdرا به وضعیت Gateway اضافه کنید.- یک فروشگاه اجاره ACPX زیر دایرکتوری وضعیت ACPX اضافه کنید.
- پیش از ایجاد یک wrapper تولیدشده، یک اجاره بنویسید.
leaseIdرا روی رکوردهای جدید نشست ACPX ذخیره کنید.- فیلدهای موجود PID و فرمان را برای رکوردهای قدیمی نگه دارید.
مرحله ۲: پاکسازی اجارهمحور
- پاکسازی بستن را تغییر دهید تا ابتدا
leaseIdرا بارگذاری کند. - پیش از سیگنال دادن، مالکیت فرایند زنده را در برابر اجاره راستیآزمایی کنید.
- جایگزین فعلی PID ریشه و ریشه wrapper را فقط برای رکوردهای قدیمی نگه دارید.
- پس از پاکسازی راستیآزماییشده، اجارهها را
closedعلامتگذاری کنید. - وقتی فرایند پیش از پاکسازی از بین رفته است، اجارهها را
lostعلامتگذاری کنید.
مرحله ۳: جمعآوری هنگام راهاندازی بهصورت اجارهمحور
- جمعآوری هنگام راهاندازی اجارههای باز را اسکن میکند.
- برای هر اجاره، فرایند ریشه را راستیآزمایی کنید و فرزندان را جمعآوری کنید.
- درختهای راستیآزماییشده را از فرزندان به والد جمعآوری کنید.
- اجارههای قدیمی
closedوlostرا با یک پنجره نگهداری محدود منقضی کنید. - اسکن نشانگر فرمان را فقط بهعنوان یک جایگزین موقت قدیمی نگه دارید، که در صورت امکان با ریشه wrapper و نمونه Gateway محافظت میشود.
مرحله ۴: ردیفهای مالکیت نشست
- فراداده مالکیت را به ردیفهای نشست Gateway اضافه کنید.
- به نویسندههای ACPX، عامل فرعی، کار پسزمینه و فروشگاه نشست بیاموزید که
ownerSessionKeyیاspawnedByرا پر کنند. - بررسیهای نمایانی نشست را به استفاده از فراداده ردیف تبدیل کنید.
- جستوجوهای ثانویه
sessions.list({ spawnedBy })در زمان نمایانی را حذف کنید.
مرحله ۵: حذف اکتشافهای قدیمی
پس از یک پنجره انتشار:
- اتکا به رشتههای ذخیرهشده فرمان ریشه برای پاکسازی ACPX غیرقدیمی را متوقف کنید
- اسکنهای نشانگر فرمان هنگام راهاندازی را حذف کنید
- جستوجوهای فهرستی جایگزین نمایانی را حذف کنید
- رفتار دفاعی شکست بسته را برای اجارههای مفقود یا غیرقابل راستیآزمایی نگه دارید
آزمونها
دو مجموعه جدولمحور اضافه کنید.
شبیهساز چرخه عمر فرایند:
- PID توسط فرایند نامرتبطی دوباره استفاده شده است
- PID توسط ریشه wrapper یک Gateway دیگر دوباره استفاده شده است
- فرمان ذخیرهشده wrapper با نقلقول shell است، فرمان زنده
psنیست - فرزند adapter خارج میشود، نوه در گروه فرایند باقی میماند
- جایگزین SIGTERM هنگام مرگ والد به SIGKILL میرسد
- فهرستگیری فرایند در دسترس نیست
- اجاره کهنه با فرایند مفقود
- یتیم راهاندازی با wrapper، فرزند adapter و نوه
ماتریس نمایانی نشست:
self،tree،agent،all- a2a فعال و غیرفعال
- ردیف همان عامل
- ردیف بینعاملی
- ردیف ACP بینعاملی ایجادشده متعلق به درخواستکننده
- درخواستکننده sandboxشده که به
treeمحدود شده است - کنشهای فهرست، تاریخچه، ارسال و وضعیت
ناوردای مهم: فرزند ایجادشده متعلق به درخواستکننده هرجا نمایانی پیکربندیشده
درخت نشست درخواستکننده را شامل شود نمایان است، و all از tree کمتوانتر نیست.
نکات سازگاری
رکوردهای قدیمی نشست ممکن است leaseId نداشته باشند. آنها باید از مسیر پاکسازی
قدیمی شکست بسته استفاده کنند:
- نیازمند یک فرایند ریشه زنده
- وقتی انتظار wrapper تولیدشده میرود، نیازمند مالکیت ریشه wrapper
- برای ریشههای غیر wrapper نیازمند توافق فرمان
- هرگز فقط بر اساس فراداده ذخیرهشده کهنه PID سیگنال ندهید
اگر یک رکورد قدیمی قابل راستیآزمایی نباشد، آن را به حال خود بگذارید. پاکسازی اجاره هنگام راهاندازی و پنجره انتشار بعدی باید در نهایت جایگزین را بازنشسته کنند.
معیارهای موفقیت
- بستن یک نشست قدیمی یا کهنه ACPX نمیتواند فرایند Gateway دیگری را بکشد.
- مرگ والد نوههای adapter سرسخت را در حال اجرا باقی نمیگذارد.
cancelنوبت فعال را بدون بستن نشستهای قابل استفاده دوباره قطع میکند.sessions_listمیتواند فرزندان ACP بینعاملی متعلق به درخواستکننده را زیر هر دوtreeوallنشان دهد.- پاکسازی هنگام راهاندازی با اجارهها هدایت میشود، نه اسکنهای گسترده رشته فرمان.
- آزمونهای متمرکز ماتریس فرایند و نمایانی، همه موارد مرزی را پوشش میدهند که پیشتر به اصلاحهای موردی در بازبینی نیاز داشتند.