Alignment — คุยให้ตรงก่อนลงมือ (Grilling)
บทที่แล้ว (MP-0) เราเห็นแล้วว่า misalignment — agent ทำไม่ตรงใจ — คือ failure mode
อันดับหนึ่งที่ README ของ mattpocock/skills พูดถึง และยาที่ใช้แก้คือ grilling
บทนี้เราจะเจาะลึกว่า grilling ทำงานยังไงจริงๆ ต่างจาก grill-me กับ grill-with-docs ยังไง
และทำไม ubiquitous language
ถึงเป็นผลพลอยได้ที่ทรงพลังของมัน ถ้าคุณเคยเจอ agent เขียนโค้ดผิดทิศทางทั้งที่คุณคิดว่าสั่งชัดแล้ว
บทนี้คือคำตอบว่าทำไม และจะป้องกันยังไงตั้งแต่ก่อนพิมพ์ prompt แรก
ปัญหา: ไม่มีใครรู้แน่ว่าตัวเองต้องการอะไร
README อ้างประโยคจาก The Pragmatic Programmer ตรงๆ ว่า "no-one knows exactly what they want" — นี่ไม่ใช่ปัญหาเฉพาะยุค AI มันคือปัญหาคลาสสิกของงานซอฟต์แวร์ทุกยุค ลูกค้าอธิบายไม่ครบ, PM เขียน ticket กว้างเกินไป, dev สองคนตีความ requirement เดียวกันคนละแบบ
พอมี agent เข้ามาช่วยเขียนโค้ด ช่องว่างนี้ยิ่งขยายใหญ่ขึ้น เพราะ agent ไม่มีบริบทที่อยู่ในหัวคุณเลย ลองนึกภาพคุณพิมพ์สั้นๆ ว่า "เพิ่ม endpoint ยกเลิกออเดอร์ให้หน่อย" ใน NestJS project — agent อาจจะเดาเอาว่ายกเลิกได้ทุกสถานะ, ไม่ต้องเช็กสิทธิ์ใคร, ลบ record ทิ้งเลย ทั้งที่ในหัวคุณคิดว่า ยกเลิกได้เฉพาะ order ที่ยังไม่ shipped, ต้องเป็นเจ้าของ order เท่านั้นถึงยกเลิกได้, และต้อง soft-delete เก็บประวัติไว้ — ช่องว่างนี้แหละที่ทำให้ agent "ทำไม่ตรงใจ" ทั้งที่คุณตั้งใจสั่งงานถูกแล้ว
Grilling: สัมภาษณ์ทีละกิ่งจนกว่าจะเคลียร์ทั้งต้น
grilling คือ skill แบบ model-invoked
ที่เป็น primitive อยู่เบื้องหลัง /grill-me และ /grill-with-docs
ตัวเนื้อหาจริงใน SKILL.md เขียนไว้แบบนี้ (แปลจากต้นฉบับ):
"สัมภาษณ์ผมอย่างไม่ปรานีในทุกแง่มุมของแผนนี้ จนกว่าเราจะเข้าใจตรงกัน เดินลงไปทีละกิ่งของ design tree แก้ dependency ระหว่างการตัดสินใจแต่ละจุดทีละอัน สำหรับทุกคำถาม ให้เสนอคำตอบที่แนะนำมาด้วย"
พูดง่ายๆ คือ agent จะมองแผนงานเป็น design tree — ต้นไม้ของการตัดสินใจ ที่แต่ละกิ่งขึ้นอยู่กับกิ่งก่อนหน้า แล้วเดินลงไปทีละกิ่ง แก้ปัญหาทีละจุดจนครบทุกกิ่ง ไม่ใช่โยนคำถามทั้งหมดมาพร้อมกัน
สองกฎที่ SKILL.md เน้นย้ำและสำคัญมากในทางปฏิบัติ:
- ถามทีละข้อเท่านั้น — ต้นฉบับเขียนตรงๆ ว่า "Asking multiple questions at once is bewildering" (ยิงหลายคำถามพร้อมกันทำให้งง) เพราะคำตอบของข้อก่อนอาจเปลี่ยนว่าข้อถัดไปควรถามอะไร
-
แยก fact ออกจาก decision — ถ้าเป็นข้อเท็จจริงที่หาได้จากโค้ด (เช่น "ตอนนี้ schema
มี field
deletedAtอยู่แล้วไหม") agent ควรไปเปิดโค้ดดูเอง ไม่ต้องถามคุณ แต่ถ้าเป็น การตัดสินใจ (เช่น "จะ soft-delete หรือ hard-delete") นั่นเป็นสิทธิ์ของคุณเสมอ ต้องถามและรอคำตอบ พร้อมข้อเสนอแนะจาก agent ประกอบการตัดสินใจ
และข้อสุดท้ายที่สำคัญไม่แพ้กัน: SKILL.md ปิดท้ายด้วย "Do not enact the plan until I confirm we have reached a shared understanding" — ห้ามเริ่มลงมือทำจริงจนกว่าคุณจะยืนยันว่าเข้าใจตรงกันแล้ว นี่คือเหตุผลที่ grilling ต้องมาก่อนเสมอ ไม่ใช่แค่เสริม
สามหน้าตาของ grilling: primitive กับ 2 ประตูทางเข้า
grilling เองเป็น model-invoked primitive — เป็นเทคนิคกลางที่ skill อื่น
เรียกใช้ซ้ำได้ ไม่ต้องประดิษฐ์การสัมภาษณ์ใหม่ทุกครั้ง ส่วน grill-me และ grill-with-docs
คือ user-invoked สองประตูที่ห่อ grilling ไว้คนละแบบ:
| Skill | เรียกด้วย | ใช้กับ | ทิ้งอะไรไว้หลังจบ |
|---|---|---|---|
grilling |
— | primitive เบื้องหลัง | ไม่มี ทำหน้าที่แค่เป็นเครื่องยนต์ของการสัมภาษณ์ |
grill-me |
/grill-me |
งานทั่วไป ไม่จำกัดว่าต้องเป็นโค้ด | ไม่มีอะไรเลย — stateless เขียนไฟล์ 0 ไฟล์ ผลลัพธ์คือความเข้าใจที่คมขึ้นในบทสนทนาเท่านั้น |
grill-with-docs |
/grill-with-docs |
งานที่มีโค้ด/โดเมนเกี่ยวข้อง | Stateful — เขียน/อัปเดต CONTEXT.md (glossary) และ ADR ใน docs/adr/ ระหว่างสัมภาษณ์ |
จาก SKILL.md ของ grill-with-docs เขียนสั้นมากแค่บรรทัดเดียว: "Run a `/grilling` session,
using the `/domain-modeling` skill" — คือมันไม่ได้เขียน logic การสัมภาษณ์ซ้ำเลย แค่บอกให้รัน grilling
พร้อมพ่วง domain-modeling (skill ที่ดูแลเรื่อง glossary/ADR) เข้าไปด้วยเท่านั้น นี่คือความหมายของ
"primitive" — ของจริงอยู่ที่ grilling ตัวเดียว ที่เหลือคือ wrapper บางๆ
เลือกยังไง: กำลังคิด design เรื่องทั่วไป (จะ deploy ยังไง, จะเขียน spec แบบไหน) ใช้ /grill-me
พอ แต่ถ้ากำลังจะแตะโค้ด/โดเมนของระบบจริง และอยากได้ศัพท์ร่วมที่ค้างอยู่ในโปรเจกต์ ให้ใช้
/grill-with-docs ตั้งแต่ต้น เพราะมันจะบันทึกสิ่งที่ตกลงกันไว้ ไม่ระเหยหายไปตอนจบ session
Ubiquitous language: ทำไม CONTEXT.md ถึงลด token ได้จริง
ผลพลอยได้ที่ทรงพลังที่สุดของ grill-with-docs คือมันสร้าง ubiquitous language
ให้กับ agent README อ้าง Eric Evans จาก Domain-Driven Design ว่า "เมื่อมีภาษาร่วมกัน บทสนทนาระหว่าง
dev และการแสดงออกในโค้ดจะมาจากโมเดลโดเมนเดียวกัน" ตัวอย่างจริงจาก README เทียบให้เห็นชัด:
| ประโยค | |
|---|---|
| BEFORE | "There's a problem when a lesson inside a section of a course is made 'real' (i.e. given a spot in the file system)" |
| AFTER | "There's a problem with the materialization cascade" |
ทั้งสองประโยคความหมายเดียวกัน แต่ประโยคที่สองสั้นกว่ามาก เพราะคำว่า materialization
ถูกนิยามไว้แล้วใน CONTEXT.md ของโปรเจกต์ — agent ไม่ต้องอธิบายซ้ำทุกครั้งว่า "การทำให้ lesson
มีที่อยู่จริงในไฟล์ระบบ" คืออะไร แค่พูดคำเดียวก็สื่อสารตรงกันได้ทันที
รูปแบบของ CONTEXT.md ตามที่ skill domain-modeling กำหนดไว้ เป็น glossary
ล้วนๆ — ไม่มีรายละเอียด implementation ปนอยู่เลย:
# Ordering
## Language
**Order**:
คำสั่งซื้อที่ลูกค้าสร้างขึ้น มีสถานะเปลี่ยนไปตามวงจรชีวิต
_Avoid_: Purchase, transaction
**Materialization**:
การทำให้ lesson ในตำแหน่งหนึ่งของ course มี "ที่อยู่จริง" บน file system
_Avoid_: การ generate ไฟล์, การ build
ข้อสำคัญจาก domain-modeling SKILL.md: ต้องเจาะจงเฉพาะศัพท์ของโดเมนนี้จริงๆ
— คำศัพท์ programming ทั่วไป (timeout, error type, utility pattern) ไม่นับ แม้จะใช้บ่อยแค่ไหนก็ตาม
และ CONTEXT.md ห้ามใส่รายละเอียด implementation, ห้ามใช้เป็น spec หรือ scratch pad
— มันคือ glossary เท่านั้น
ประโยชน์ที่ได้ไม่ได้มีแค่ลดความยาว: ตัวแปร ฟังก์ชัน และไฟล์ในโค้ดถูกตั้งชื่อสอดคล้องกันตามภาษาร่วมเดียวกัน
ทำให้ codebase เดินสำรวจง่ายขึ้นสำหรับทั้งคนและ agent และ agent ใช้ token คิดน้อยลง เพราะมีภาษาที่กระชับกว่าให้ใช้
— ผูกกับงานคุณตรงๆ: ถ้า service ของคุณเรียก field materializedAt ตรงกับที่นิยามใน CONTEXT.md
agent จะเดาชื่อ field ถูกตั้งแต่ครั้งแรก ไม่ต้องลองผิดลองถูก
ผูกกับงานจริง: grill ก่อนทำฟีเจอร์ใน NestJS
สมมติคุณจะสั่ง agent ว่า "เพิ่ม endpoint ยกเลิกออเดอร์ (cancel order) ให้หน่อย" ใน NestJS service
ถ้าใช้ /grill-with-docs ก่อนเริ่มเขียนโค้ด agent ควรถามประมาณนี้ ทีละข้อ:
// รอบที่ 1 — agent ถาม (fact เช็กจากโค้ดเองก่อนแล้ว ไม่ถามซ้ำ)
Agent: จากที่ดู OrderEntity ตอนนี้มีสถานะ PENDING, PAID, SHIPPED, CANCELLED
ยกเลิกได้เฉพาะ PENDING กับ PAID ใช่ไหม? (แนะนำ: ใช่ เพราะ SHIPPED แล้วยกเลิกจะกระทบ stock/คลัง)
// รอบที่ 2 — รอคำตอบก่อน แล้วค่อยถามข้อถัดไป
Agent: ใครมีสิทธิ์ยกเลิก order ได้บ้าง? เฉพาะเจ้าของ order เอง หรือ admin ก็ยกเลิกแทนได้ด้วย?
(แนะนำ: ทั้งคู่ — เจ้าของยกเลิกเองได้ ส่วน admin ยกเลิกแทนได้แต่ต้อง log ว่าใครกดแทนใคร)
// รอบที่ 3 — decision ที่กระทบ schema โดยตรง
Agent: ยกเลิกแล้วลบ record จริง (hard-delete) หรือแค่เปลี่ยนสถานะเป็น CANCELLED (soft-delete)?
(แนะนำ: soft-delete เพราะต้องเก็บประวัติไว้ทำ report และ audit)
สังเกตว่าทุกคำถามมี คำตอบแนะนำ (recommended answer) แนบมาด้วยเสมอ — คุณแค่ตอบรับ,
ปฏิเสธ, หรือแก้ไขสั้นๆ ไม่ต้องคิดคำตอบจากศูนย์ทุกข้อ นี่คือสิ่งที่ทำให้ grilling เร็วในทางปฏิบัติ
ทั้งที่ฟังดูเหมือนต้องตอบเยอะ พอจบ 3 รอบนี้ grill-with-docs จะเขียนคำว่า Order,
CANCELLED ลง CONTEXT.md (ถ้ายังไม่มี) และถ้าการตัดสินใจ soft-delete ถือว่า
"ยากจะย้อนกลับ" (hard to reverse) พอจะเสนอเขียนเป็น ADR ให้ด้วย
Pitfall ที่พบบ่อย: ปล่อยให้ agent ยิงคำถามรวดเดียวเป็นชุด 5-6 ข้อในข้อความเดียว —
SKILL.md เตือนตรงๆ ว่านี่ "bewildering" เพราะคำตอบข้อแรกอาจทำให้คำถามข้อสามไม่จำเป็นอีกต่อไป
ถ้าเจอแบบนี้ ให้บอก agent ตรงๆ ว่า "ถามทีละข้อ" อีก pitfall คือปล่อยให้ agent เขียนรายละเอียด
implementation ลง CONTEXT.md — ถ้าเห็นแบบนั้นให้เตือนกลับ เพราะมันควรเป็น glossary ล้วนๆ เท่านั้น
- นึกถึงฟีเจอร์เล็กๆ ที่คุณกำลังจะทำจริง (หรือเคยทำ) ใน repo ของคุณเอง — เขียน design tree ของมันมา 3-4 กิ่ง (decision ที่ต้องตัดสินใจ) แบบในอินโฟกราฟิกด้านบน แล้วบอกว่าข้อไหนเป็น fact (หาจากโค้ดได้) กับข้อไหนเป็น decision (ต้องเป็นคุณตัดสินเท่านั้น)
-
พิมพ์ฟีเจอร์นั้นมาในแชท แล้วให้ผมเล่นบทบาท
grill-with-docsถามคุณทีละข้อ พร้อมเสนอคำตอบแนะนำ — ลองตอบไปเรื่อยๆ จนกว่าเราจะสรุปได้ว่า "เข้าใจตรงกันแล้ว" -
จากบทสนทนาข้อ 2 ลองร่างเนื้อหา
CONTEXT.mdเองสัก 1-2 term ตามฟอร์แมต**Term**: คำอธิบาย / _Avoid_: คำที่ไม่ใช้— เช็กว่าเนื้อหาที่เขียนไม่มี implementation detail หลุดเข้าไปเลยสักบรรทัด