ความสามารถ Spawn Agent — ENDMEMEX สั่ง Claude/Codex อีกตัวทำงานแทนแบบมีขอบเขต

ภาพประกอบ: ความสามารถ Spawn Agent — ENDMEMEX สั่ง Claude/Codex อีกตัวทำงานแทนแบบมีขอบเขต

ENDMEMEX ให้ agent หนึ่งเริ่ม ติดตาม และยกเลิกงานของอีก agent ได้ผ่าน endeavor_agent_start, endeavor_agent_status และ endeavor_agent_cancel โดยกำหนดขอบเขตงานชัดเจน เหมาะกับงานที่แบ่งเป็นส่วนย่อยได้

ความสามารถนี้คืออะไร

endeavor_agent_start เริ่ม sub-agent (Codex หรือ Claude) รันงานหนึ่งชิ้นแบบbounded (มีขอบเขต, มี timeout, มีเป้าหมายชัดเจน) ในเบื้องหลัง คืน run_id กลับมาทันที — agent ที่เรียกใช้ต้องpoll สถานะต่อด้วย endeavor_agent_status (ได้ผลลัพธ์บางส่วนระหว่างทางด้วย ไม่ต้องรอจนจบ) และยกเลิกได้ด้วย endeavor_agent_cancel

ใช้เมื่อ: ต้องการความเห็นที่สอง (second opinion) ต่อ diff หนึ่งชิ้น, ต้องการวิเคราะห์แบบขนาน, หรือมีงานที่ agent อีกตัวถูกเทรนมาให้ถนัดกว่า — ทั้งหมดนี้เกิดขึ้นโดยผู้ใช้ไม่ต้องสลับเครื่องมือเอง

จุดออกแบบที่สำคัญที่สุด: แยก MCP server ต่างหากโดยเจตนา

คอมเมนต์ต้นไฟล์ agent_mcp_server.py อธิบายเหตุผลตรงๆ: “This server deliberately stays separate from mcp_server.py: delegation can start external CLI/model processes, while the existing server remains a project-memory-only trust and failure boundary.”

หมายความว่า ENDMEMEX มี MCP server 2 ตัวที่ทำงานคนละหน้าที่โดยตั้งใจ:

  • mcp_server.py — จัดการความจำโปรเจกต์เท่านั้น (query, checkpoint, record ฯลฯ) ไม่มีทางรันโปรเซสภายนอกได้
  • agent_mcp_server.py — จัดการการสั่งงาน sub-agent เท่านั้น ซึ่งหมายถึงการรันโปรเซสภายนอกจริง

การแยกนี้ทำให้ “ความจำเสียหาย” กับ “โปรเซสภายนอกหลุดควบคุม” เป็นความเสี่ยงคนละชั้นที่ไม่ปนกัน — ถ้า memory server มีบั๊ก ผลกระทบจำกัดอยู่ที่ข้อมูลความจำเท่านั้น ไม่มีทางลามไปเป็นการรันโปรเซสที่ควบคุมไม่ได้

Role policy: worker เขียนได้ แต่ reviewer/advisor ต้องอ่านอย่างเดียวเสมอ

การเรียก endeavor_agent_start ต้องระบุ role เป็นหนึ่งใน 3 แบบ ที่แต่ละแบบมีสิทธิ์ต่างกันแบบบังคับด้วยโค้ด (role_policy_error ตรวจสอบก่อนรันทุกครั้ง):

  • worker (ดีฟอลต์) — เขียนไฟล์ได้ถ้าขอ access=workspace_write ชัดเจน
  • reviewer — ต้องเป็น read-only เสมอ ถ้าเป็น Codex และ sandbox ไม่ใช่ read-only จะถูกปฏิเสธทันที
  • advisor — เช่นเดียวกับ reviewer บังคับ read-only เสมอ

สำหรับ Claude sub-agent ที่ role ไม่ใช่ worker ระบบยังเช็คเพิ่มว่า tool ที่ขอ (allowed_tools) ต้องอยู่ใน READ_ONLY_CLAUDE_TOOLS เท่านั้น — ถ้าขอ tool ที่เขียนได้ (เช่น Edit, Write) ทั้งที่ role เป็น reviewer จะถูกปฏิเสธก่อนแม้แต่จะเริ่มรัน

แม้ role เป็น worker และขอ workspace_write ได้จริง Claude worker ก็ได้แค่เครื่องมือแก้ไข (Read/Grep/Glob/Edit/Write) โดยไม่มี Bash เลย — ป้องกันไม่ให้ sub-agent รันคำสั่งระบบใดๆ แม้จะเขียนไฟล์ได้

Admission control: กันการ spawn agent วนไม่หยุด

ก่อนจะยอม start agent ใหม่จริง มีการตรวจสอบ 2 ชั้นภายใต้ file lock (fcntl.flock) เพื่อกัน race condition เวลามีการเรียกพร้อมกันหลาย process:

  1. จำนวน run ที่ยัง active พร้อมกัน ต้องไม่เกิน 4 — นับจาก state file ของทุก run ที่ยังไม่ถึงสถานะ terminal (completed/failed/timed_out/cancelled)
  2. อัตราการ start ต้องไม่เกิน 6 ครั้งต่อ 60 วินาที — ป้องกันการ spawn ถี่เกินไปแม้จะไม่มี run ค้างอยู่ก็ตาม

ทั้งสองเงื่อนไขทำงานผ่านไฟล์บนดิสก์ที่แชร์ระหว่าง process (ไม่ใช่ตัวแปรใน memory) เพราะแต่ละครั้งที่ MCP client เรียก tool อาจเป็นคนละ process กัน — ถ้าใช้ตัวแปรใน memory จะนับจำนวนไม่ถูกต้องข้าม process

ป้องกัน PID reuse race ด้วย “birth token”

จุดที่ละเอียดที่สุดในระบบนี้: การเช็คว่า sub-agent process ยัง “มีชีวิตอยู่” จริงหรือไม่ ไม่ได้เช็คแค่ PID ยังมีอยู่ในระบบหรือเปล่า เพราะ PID สามารถถูก reuse โดย process อื่นที่ไม่เกี่ยวข้องได้ หลัง process เดิมตายไปแล้ว — ถ้าเช็คแค่ PID เฉยๆ อาจนับ process คนละตัวที่บังเอิญได้ PID เดิมมาว่ายัง “active” อยู่ ทำให้ concurrency limit นับผิด

ทางแก้คือเก็บ “birth token” (process_start_token) ไว้ตอน spawn process แล้วเทียบซ้ำตอนเช็คสถานะ — ถ้า token ไม่ตรงกัน แปลว่า PID นั้นถูก process อื่น reuse ไปแล้ว ไม่ใช่ตัวเดิม คอมเมนต์ในซอร์สอธิบายเหตุผลของ fallback ตรงๆ: “undercounting active runs would let admission exceed the concurrency limit, which is less safe than a rare overcount” — ถ้าคำนวณ token ไม่ได้ (ทั้งสองฝั่ง) ระบบเลือกนับว่ายัง active อยู่ (overcount) แทนที่จะเสี่ยงนับต่ำเกินจริงจนปล่อยให้ concurrency limit ถูกละเมิด

Cancel ปลอดภัยเสมอ แม้เรียกซ้ำ

endeavor_agent_cancel ออกแบบให้เรียกซ้ำได้อย่างปลอดภัย (idempotent) — ยกเลิก run ที่จบไปแล้วซ้ำไม่ทำให้เกิด error และมีรายละเอียดความปลอดภัยสำคัญ: MCP control process ไม่เคยส่งสัญญาณ kill ไปยัง process group ID (PGID) ที่อ่านมาจากไฟล์บนดิสก์โดยตรง — มีแต่ run manager ตัวที่ยังมีชีวิตอยู่จริงเท่านั้นที่ signal และ reap process group ของตัวเอง เหตุผลตรงไปตรงมา: PGID ที่อ่านจากไฟล์เก่าอาจถูก reuse ไปเป็นของ process อื่นแล้ว (ปัญหาเดียวกับ PID reuse ข้างบน) การ kill ตรงจากไฟล์เสี่ยงไป kill process ที่ไม่เกี่ยวข้อง

Depth cap: ห้าม sub-agent spawn agent ต่อ

Docstring ของ agent_delegate.py ระบุกฎเข้มงวด: “Never ask the child to delegate onward — depth is capped at 1 and the nested call will fail with exit 2” — sub-agent ที่ถูก spawn ขึ้นมาห้าม spawn agent ตัวที่สามต่อ ถ้าพยายามทำจะถูกปฏิเสธทันทีด้วย exit code 2 การจำกัดความลึกไว้แค่ 1 ชั้นป้องกันปัญหา exponential fan-out (agent spawn agent spawn agent ไม่รู้จบ) ที่จะทำให้ resource หมดและควบคุมไม่ได้

Sub-agent เริ่มต้นแบบ “เย็น” เสมอ — ต้องใส่บริบทเองทุกครั้ง

จุดสำคัญที่ agent ที่เรียกใช้ต้องรู้: “the child starts cold — it knows nothing about your session” — sub-agent ที่ถูก spawn ไม่มีความทรงจำใดๆ จากบทสนทนาของ agent ที่เรียกมันเลย ต้องใส่บริบทที่จำเป็นเข้าไปใน prompt ตรงๆ หรือบอกให้มันไปอ่าน ENDMEMEX handoff เอง (python3 ENDMEMEX/endeavor_db.py handoff --project <P> --json) — ทุกงานที่มอบหมายต้องเป็นงานเดียวที่มีขอบเขตชัดเจน พร้อม deliverable ที่ระบุไว้ ไม่ใช่ภารกิจเปิดกว้างที่ไม่รู้จบ

ผลลัพธ์ไม่ถูกผลักกลับเข้า context อัตโนมัติ — ต้อง poll เอง

ข้อควรระวังที่สำคัญที่สุดสำหรับโหมด background: “completion is not pushed back into your LLM context” — เมื่อ sub-agent ทำงานเสร็จ ผลลัพธ์ไม่ได้ปรากฏขึ้นในบทสนทนาของ agent ที่เรียกมันเองโดยอัตโนมัติ ต้อง poll endeavor_agent_status เอง หรือใช้กลไก endeavor_memory_event_poll/endeavor_memory_event_ack (ระบบ event แบบ at-least-once ที่ publish event เมื่องาน background เสร็จในขอบเขตโปรเจกต์) เพื่อให้ host/orchestrator รู้ว่าต้องกลับมาอ่านผลลัพธ์เมื่อไร — ถ้าลืมจัดการ polling ผลลัพธ์ของงานที่ spawn ไปอาจไม่มีใครอ่านเลย

ทำไมเรื่องนี้ถึงสำคัญ

ความสามารถ spawn agent ของ ENDMEMEX แสดงสถาปัตยกรรมที่ระมัดระวังเรื่องความปลอดภัยของกระบวนการทำงานพร้อมกันในระดับที่ไม่ค่อยเห็นในเครื่องมือ AI agent ทั่วไป — ตั้งแต่การแยก trust boundary ทั้ง MCP server, role-based policy ที่บังคับด้วยโค้ดไม่ใช่แค่คำแนะนำ, admission control ที่กันการ spawn ล้นเครื่อง, ไปจนถึงการป้องกัน PID/PGID reuse race ที่ผิดพลาดได้ยากในโค้ดทั่วไปแต่มีจริงเมื่อ process จำนวนมากเกิด-ตายเร็ว — ทั้งหมดนี้จำเป็นเพราะการ “spawn agent” คือการยกระดับจากงานที่ทำภายใน process เดียว ไปสู่การควบคุมโปรเซสภายนอกจริงๆ ซึ่งมีความเสี่ยงต่างชั้นไปเลยจากการเรียก tool ทั่วไป

อ่านเพิ่มเติม

ความคิดเห็น

กำลังโหลดความคิดเห็น...