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 ควรมีรหัสอินสแตนซ์รันไทม์ที่คงที่:

ts
type GatewayInstanceId = string;

รหัสนี้สามารถสร้างขึ้นเมื่อ Gateway เริ่มทำงานและเก็บไว้ในสถานะตลอดอายุของ การติดตั้งนั้น รหัสนี้ไม่ใช่ข้อมูลลับด้านความปลอดภัย แต่เป็นตัวจำแนกความเป็นเจ้าของที่ใช้ เพื่อหลีกเลี่ยงการสับสนระหว่างโปรเซส ACP ของ Gateway หนึ่งกับโปรเซสของ Gateway อื่น

ความเป็นเจ้าของเซสชัน ACP

ทุกเซสชัน ACP ที่สร้างขึ้นควรมีข้อมูลเมตาความเป็นเจ้าของที่ปรับให้อยู่ในรูปแบบมาตรฐาน:

ts
type AcpSessionOwner = {  sessionKey: string;  spawnedBy?: string;  parentSessionKey?: string;  ownerSessionKey: string;  agentId: string;  backend: "acpx";  gatewayInstanceId: GatewayInstanceId;  createdAt: number;};

Gateway ควรส่งคืนฟิลด์เหล่านี้ในแถวเซสชันเมื่อทราบค่า การกรองการมองเห็นควรเป็นการตรวจสอบโดยตรงจากข้อมูลเมตาของแถว:

ts
canSeeSessionRow({  row,  requesterSessionKey,  visibility,  a2aPolicy,});

วิธีนี้จะกำจัดการเรียก sessions.list({ spawnedBy }) รองที่ซ่อนอยู่จาก การตรวจสอบการมองเห็น เซสชันลูก ACP ข้ามเอเจนต์ที่สร้างขึ้นถือว่าเป็นของผู้ร้องขอ เพราะ แถวระบุไว้เช่นนั้น ไม่ใช่เพราะคำค้นที่สองบังเอิญค้นพบเซสชันนั้น

สัญญาเช่าโปรเซส ACPX

ทุกการเริ่ม wrapper ที่สร้างขึ้นควรสร้างระเบียนสัญญาเช่า:

ts
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 ผ่าน สภาพแวดล้อมของโปรเซส:

sh
OPENCLAW_ACPX_LEASE_ID=...OPENCLAW_GATEWAY_INSTANCE_ID=...

เมื่อแพลตฟอร์มรองรับ การตรวจสอบควรให้ความสำคัญกับข้อมูลเมตาของโปรเซสที่กำลังทำงาน ซึ่งไม่อาจสับสนจากการใส่เครื่องหมายอัญประกาศในคำสั่ง:

  • PID รากยังคงมีอยู่
  • พาธ wrapper ที่กำลังทำงานอยู่ภายใต้ wrapperRoot
  • กลุ่มโปรเซสตรงกับสัญญาเช่าเมื่อมีข้อมูล
  • สภาพแวดล้อมมีรหัสสัญญาเช่าที่คาดไว้เมื่อสามารถอ่านได้
  • แฮชคำสั่งหรือพาธไฟล์ปฏิบัติการตรงกับสัญญาเช่า

หากไม่สามารถตรวจสอบโปรเซสที่กำลังทำงานได้ การล้างต้องปฏิเสธการดำเนินการเพื่อความปลอดภัย

ตัวควบคุมวงจรชีวิต

เพิ่มตัวควบคุมวงจรชีวิต ACPX หนึ่งตัวซึ่งเป็นเจ้าของสัญญาเช่าโปรเซสและนโยบาย การล้าง:

ts
interface AcpxLifecycleController {  ensureSession(input: AcpRuntimeEnsureInput): Promise&lt;AcpRuntimeHandle&gt;;  cancelTurn(handle: AcpRuntimeHandle): Promise<void>;  closeSession(input: {    handle: AcpRuntimeHandle;    discardPersistentState?: boolean;    reason?: string;  }): Promise<void>;  reapStartupOrphans(): Promise<void>;  verifyOwnedTree(lease: AcpxProcessLease): Promise&lt;OwnedProcessTree | null&gt;;}

cancelTurn ร้องขอให้ยกเลิกรอบการทำงานเท่านั้น ต้องไม่เก็บกวาด wrapper หรือโปรเซสอะแดปเตอร์ที่นำกลับมาใช้ใหม่ได้

closeSession ได้รับอนุญาตให้เก็บกวาด แต่เฉพาะหลังจากโหลดระเบียนเซสชัน โหลดสัญญาเช่า และตรวจสอบว่าต้นไม้โปรเซสที่กำลังทำงานยังคงเป็นของ สัญญาเช่านั้น

reapStartupOrphans เริ่มจากสัญญาเช่าที่เปิดอยู่ในสถานะ โดยอาจใช้ตารางโปรเซส เพื่อค้นหาโปรเซสลูกหลาน แต่ไม่ควรสแกนคำสั่งใด ๆ ที่ดูคล้าย ACP ก่อน แล้วจึงตัดสินในภายหลังว่าคำสั่งเหล่านั้นน่าจะเป็นของเรา

สัญญาของ Wrapper

wrapper ที่สร้างขึ้นควรมีขนาดเล็ก โดยควร:

  • เริ่มอะแดปเตอร์ในกลุ่มโปรเซสเมื่อแพลตฟอร์มรองรับ
  • ส่งต่อสัญญาณยุติตามปกติไปยังกลุ่มโปรเซส
  • ตรวจจับการตายของโปรเซสแม่
  • เมื่อโปรเซสแม่ตาย ให้ส่ง SIGTERM แล้วคงให้ wrapper ทำงานต่อไปจนกว่า กลไกสำรอง SIGKILL จะทำงาน
  • รายงาน PID รากและรหัสกลุ่มโปรเซสกลับไปยังตัวควบคุมวงจรชีวิตเมื่อ มีข้อมูลดังกล่าว

wrapper ไม่ควรตัดสินนโยบายเซสชัน แต่ควรบังคับใช้เฉพาะการล้างต้นไม้โปรเซส ภายในเครื่องสำหรับกลุ่มอะแดปเตอร์ของตนเอง

สัญญาการมองเห็นเซสชัน

การมองเห็นควรใช้ความเป็นเจ้าของของแถวที่ปรับให้อยู่ในรูปแบบมาตรฐาน:

ts
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
  • การล้างตอนเริ่มทำงานขับเคลื่อนด้วยสัญญาเช่า ไม่ใช่การสแกนสตริงคำสั่งแบบกว้าง
  • การทดสอบเมทริกซ์โปรเซสและการมองเห็นเฉพาะจุดครอบคลุมทุกกรณีขอบที่ ก่อนหน้านี้ต้องแก้ไขเฉพาะกิจระหว่างการตรวจสอบ
Was this useful?
On this page

On this page