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 ข้ามเอเจนต์ที่สร้างขึ้นและผู้ร้องขอเป็นเจ้าของ
- ผู้ร้องขอในแซนด์บ็อกซ์ที่ถูกจำกัดไว้ที่
tree - การดำเนินการแสดงรายการ, ประวัติ, ส่ง และสถานะ
ข้อกำหนดคงตัวที่สำคัญ: เซสชันลูกที่สร้างขึ้นและผู้ร้องขอเป็นเจ้าของต้องมองเห็นได้ทุกที่
ที่การตั้งค่าการมองเห็นรวมต้นไม้เซสชันของผู้ร้องขอ และ all ต้องมีความสามารถ
ไม่น้อยกว่า tree
หมายเหตุด้านความเข้ากันได้
ระเบียนเซสชันเก่าอาจไม่มี leaseId ระเบียนเหล่านี้ควรใช้เส้นทางการล้างแบบเก่า
ที่ปฏิเสธการดำเนินการเพื่อความปลอดภัย:
- ต้องมีโปรเซสรากที่กำลังทำงานอยู่
- ต้องยืนยันความเป็นเจ้าของของราก wrapper เมื่อคาดว่าจะมี wrapper ที่สร้างขึ้น
- ต้องให้คำสั่งสอดคล้องกันสำหรับรากที่ไม่ใช่ wrapper
- ห้ามส่งสัญญาณโดยอาศัยเพียงข้อมูลเมตา PID ที่จัดเก็บไว้และล้าสมัย
หากไม่สามารถตรวจสอบระเบียนเก่าได้ ให้ปล่อยไว้ตามเดิม การล้างสัญญาเช่าตอนเริ่มทำงานและ ช่วงเวลารุ่นถัดไปควรเลิกใช้กลไกสำรองนี้ได้ในที่สุด
เกณฑ์ความสำเร็จ
- การปิดเซสชัน ACPX เก่าหรือค้างต้องไม่สามารถยุติโปรเซสของ Gateway อื่นได้
- การตายของโปรเซสแม่ต้องไม่ทิ้งโปรเซสหลานของอะแดปเตอร์ที่ดื้อดึงให้ทำงานต่อ
cancelยกเลิกรอบการทำงานที่กำลังใช้งานโดยไม่ปิดเซสชันที่นำกลับมาใช้ใหม่ได้sessions_listสามารถแสดงเซสชันลูก ACP ข้ามเอเจนต์ที่ผู้ร้องขอเป็นเจ้าของได้ทั้งภายใต้treeและall- การล้างตอนเริ่มทำงานขับเคลื่อนด้วยสัญญาเช่า ไม่ใช่การสแกนสตริงคำสั่งแบบกว้าง
- การทดสอบเมทริกซ์โปรเซสและการมองเห็นเฉพาะจุดครอบคลุมทุกกรณีขอบที่ ก่อนหน้านี้ต้องแก้ไขเฉพาะกิจระหว่างการตรวจสอบ