Tool: write_file — เขียนไฟล์แบบ atomic ในขอบเขตที่ล็อกไว้เท่านั้น

ภาพประกอบ: Tool: write_file — เขียนไฟล์แบบ atomic ในขอบเขตที่ล็อกไว้เท่านั้น

บทความ Tool: read_file พูดถึงการอ่านไฟล์ บทความนี้ต่อด้วย tool ที่ตรงข้ามกัน — write_file

Tool นี้คืออะไร

เขียนข้อความลงไฟล์ตาม path ที่ระบุ — ถ้าไฟล์มีอยู่แล้วจะปฏิเสธไม่เขียนทับโดยดีฟอลต์ ต้องส่ง overwrite="true" มาชัดเจนถึงจะเขียนทับได้ เป็นเบรกความปลอดภัยง่ายๆ ที่กันไฟล์สำคัญถูกเขียนทับโดยไม่ตั้งใจ

Agent เรียก tool นี้เมื่อไร

ใช้เมื่อผู้ใช้ขอให้บันทึก/สร้าง/เขียนไฟล์ผลลัพธ์ลงดิสก์จริง — ต่างจาก read_file/rag_search ที่แค่ดึงข้อมูลเข้ามาให้โมเดลอ่าน write_file เป็น tool เดียวใน 6 ตัวของ AGENT LITE ที่เปลี่ยนแปลงสถานะจริงบนเครื่อง (สร้าง/แก้ไฟล์) จึงมีข้อจำกัดขอบเขตที่เข้มงวดกว่า tool อื่น

ขอบเขตการเขียน: จำกัดอยู่ใต้ workspect เท่านั้น

จุดออกแบบที่สำคัญที่สุดของ tool นี้: เขียนได้เฉพาะใต้ config.BASH_ALLOWED_ROOT (โฟลเดอร์ workspect/ ของโปรเจกต์เอง) เท่านั้น — ไม่ใช่ที่ไหนในเครื่องก็ได้ ทุก path ที่ส่งมาต้อง resolve แล้วอยู่ใต้ path นี้จริงๆ (เช็คด้วย Path.relative_to() ซึ่ง raise error ทันทีถ้า path หลุดออกนอกขอบเขต) ถ้าไม่ผ่านจะได้ error กลับไปตรงๆ ไม่มีการเขียนเกิดขึ้นเลย

หมายความว่า agent เขียนทับซอร์สโค้ดของตัวเองไม่ได้ และเขียนไฟล์ในโฟลเดอร์อื่นของผู้ใช้ (Desktop, Documents) ไม่ได้เลย — ไม่ว่าโมเดลจะพยายามส่ง path แบบไหนมาก็ตาม การจำกัดขอบเขตนี้ทำที่ระดับโค้ด ไม่ใช่ระดับ prompt ที่ขอให้โมเดล “อย่าเขียนนอก workspect” (ซึ่งพึ่งพาไม่ได้กับโมเดลขนาดเล็ก)

เขียนแบบ atomic — กันไฟล์เสียหายครึ่งๆ กลางๆ

ถ้าเขียนไฟล์ตรงๆ (เปิดไฟล์ปลายทางแล้วเขียนทับทีละ byte) แล้ว process ถูกขัดจังหวะกลางทาง (crash, timeout, ปิดโปรแกรม) ไฟล์ปลายทางจะเหลือข้อมูลครึ่งๆ กลางๆ เสียหายถาวร — write_file จึงใช้เทคนิคเขียนลงไฟล์ชั่วคราวก่อน แล้วค่อย rename ทับไฟล์จริงทีเดียว (os.replace):

1. เขียนเนื้อหาทั้งหมดลง <path>.tmp
2. flush() + fsync() — บังคับให้ OS เขียนลง disk จริง ไม่ใช่ค้างอยู่ใน buffer
3. os.replace(tmp, path) — เปลี่ยนชื่อไฟล์ทีเดียว (atomic ในระดับ filesystem)

os.replace เป็นการดำเนินการที่ atomic ในระดับ filesystem — ไม่มีสถานะกึ่งกลางที่ไฟล์ปลายทางเสียหายบางส่วน ผลลัพธ์มีแค่ 2 แบบ: เขียนสำเร็จสมบูรณ์ หรือไฟล์เดิมยังอยู่ครบเหมือนไม่มีอะไรเกิดขึ้น ไม่มีกรณีที่สาม

ถ้าเกิด exception ระหว่างทาง ระบบจะพยายามลบไฟล์ .tmp ทิ้งด้วย (cleanup) ไม่ปล่อยให้ไฟล์ชั่วคราวค้างอยู่

Feedback ที่ให้กลับมีรายละเอียดพอให้ตรวจสอบได้

ผลลัพธ์ที่คืนกลับไม่ใช่แค่ “เขียนสำเร็จ” เฉยๆ แต่บอกขนาดไฟล์ใหม่ (จำนวนตัวอักษร, จำนวนบรรทัด) และถ้าเป็นการเขียนทับ จะบอกขนาดไฟล์เดิมก่อนถูกแทนที่ด้วย — ให้ทั้งโมเดลและผู้ใช้ที่อ่าน log เห็นชัดว่าเกิดอะไรขึ้นจริง ไม่ต้องเดา

ทำไม tool นี้ถูกตัดทอนจากเวอร์ชันเดิมของ MAX

คอมเมนต์ในซอร์สระบุว่า write_file เวอร์ชันนี้ตัดความสามารถบางอย่างออกจากเวอร์ชันต้นทาง (ENDEAVOR_LOCAL_AGENT_MAX_VLM) โดยตั้งใจ: ไม่มีการเขียนแบบ chunk 256KB ทีละส่วน (ไม่จำเป็นที่ขนาดไฟล์ที่ agent นี้เขียนจริง) และไม่มีพฤติกรรม “สร้างไฟล์ .edited คู่กันไว้” สำหรับไฟล์ภายนอกที่มีอยู่ก่อน — Lite เลือกความเรียบง่ายและขอบเขตที่แคบกว่า เพราะเป้าหมายต่างกัน (agent สำหรับงานทั่วไปบนเครื่องเล็ก ไม่ใช่ agent แก้โค้ดโปรเจกต์ใหญ่)

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

write_file เป็นตัวอย่างที่ดีของหลักการ “ให้ความสามารถน้อยที่สุดที่จำเป็น” (least privilege) — แทนที่จะให้ agent เขียนไฟล์ได้ทุกที่แล้วหวังว่าโมเดลจะระวังเอง ระบบล็อกขอบเขตไว้แน่นตั้งแต่ระดับโค้ด ทำให้ต่อให้โมเดลพลาดหรือถูกชักจูงผิดทาง (เช่นจากเนื้อหาที่อ่านมาจากไฟล์อื่น) ความเสียหายที่เกิดได้จริงก็ยังจำกัดอยู่แค่ในโฟลเดอร์ที่ตั้งใจให้เขียนได้เท่านั้น

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

ความคิดเห็น

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