เอเจนต์เขียนโค้ดมีความมั่นใจ รวดเร็ว และไม่มีความเข้าใจด้านสถาปัตยกรรมเกี่ยวกับฐานโค้ดของคุณเลยจนกว่าคุณจะบอกพวกเขาเป็นอย่างอื่น ลองยื่นตั๋วงานที่ไม่ชัดเจนให้กับ Claude Code หรือ Codex แล้วมันจะเขียนโค้ดที่คอมไพล์ได้ ผ่านการทดสอบอย่างรวดเร็ว และละเมิดขอบเขตระหว่างเลเยอร์โดเมนและเลเยอร์ HTTP ของคุณอย่างเงียบๆ เอเจนต์ไม่ได้อ่านเอกสารการออกแบบของคุณ มันอ่านไฟล์ที่มันมองเห็น จับคู่รูปแบบ และคาดเดา ไฟล์ `DESIGN.md` แก้ปัญหาการคาดเดาโดยการบันทึกเจตนารมณ์ทางสถาปัตยกรรมของคุณไว้ในที่เดียวที่เอเจนต์มักจะมองหาเสมอ: นั่นคือใน repository เอง
TL;DR
`DESIGN.md` คือไฟล์ repository ที่ใช้ตามข้อตกลงของชุมชน ซึ่งบันทึกเจตนารมณ์ทางสถาปัตยกรรม ข้อจำกัด และการตัดสินใจด้านการออกแบบของฐานโค้ดในรูปแบบ Markdown ธรรมดา เพื่อให้เอเจนต์เขียนโค้ด (Claude Code, Codex, Cursor) สร้างโค้ดที่เข้ากับระบบ แทนที่จะขัดแย้งกับมัน มันตอบคำถามว่า "ทำไมโค้ดถึงมีรูปร่างแบบนี้" ในขณะที่ `AGENTS.md` ตอบคำถามว่า "ฉันจะสร้างและทดสอบได้อย่างไร"
บทนำ
นี่คือรูปแบบความล้มเหลวที่ทุกทีมที่นำเอเจนต์เขียนโค้ดมาใช้จะเจอภายในหนึ่งสัปดาห์ คุณขอให้เอเจนต์เพิ่มเอนด์พอยต์สำหรับการคืนเงินให้กับบริการชำระเงิน มันจะคืนแฮนเดิลที่ทำงานได้ซึ่งเรียกฐานข้อมูลโดยตรงจากคอนโทรลเลอร์ กลืนข้อผิดพลาดของเกตเวย์ และสร้างประเภทสกุลเงินใหม่ขึ้นมาเพราะมันไม่สังเกตเห็นว่าคุณมีอยู่แล้ว Diff ดูสะอาดตา การทดสอบผ่าน มันผิดสามจุดที่นักรีวิวที่รู้สถาปัตยกรรมเท่านั้นที่สามารถจับได้ เอเจนต์ไม่ได้เขียนโค้ดไม่เก่ง เพียงแต่มันมองไม่เห็นการตัดสินใจที่อยู่ในหัวของคุณ ในหน้า Notion หรือใน Slack thread เมื่อแปดเดือนที่แล้ว
`DESIGN.md` คือคำตอบที่ทีมจำนวนมากขึ้นเรื่อยๆ ได้มาบรรจบกัน มันเป็นไฟล์ Markdown ไฟล์เดียวที่คอมมิตไปยัง root ของ repository ซึ่งบอกเอเจนต์ถึงข้อเท็จจริงสำคัญเกี่ยวกับระบบของคุณ: กฎการแบ่งชั้น กฎที่ต้องคงที่เสมอที่ห้ามละเมิด รูปแบบที่คุณเลือกโดยตั้งใจ และรูปแบบที่คุณปฏิเสธ มันไม่ใช่ข้อกำหนดของผู้ขายและไม่มีคณะกรรมการที่เป็นเจ้าของมัน มันเป็นข้อตกลง เช่นเดียวกับ `ARCHITECTURE.md` และ `CONTRIBUTING.md` เป็นข้อตกลง แต่มันเข้ากันได้ดีกับไฟล์คำสั่งเฉพาะเครื่องมือที่เอเจนต์อ่านอยู่แล้ว และสำหรับงาน API และแบ็กเอนด์ มันเป็นเอกสารที่มีประโยชน์สูงสุดอย่างหนึ่งที่คุณสามารถเขียนได้
`DESIGN.md` คืออะไรกันแน่
`DESIGN.md` คือบันทึกข้อความธรรมดาของ *ทำไมโค้ดของคุณถึงมีรูปร่างแบบนั้น* ไม่ใช่ว่ามันทำอะไร (นั่นคือ README) ไม่ใช่ว่าจะรันมันอย่างไร (นั่นคือ `AGENTS.md`) แต่เป็นเหตุผลที่วิศวกรอาวุโสจะอธิบายให้พนักงานใหม่ฟังในวันแรกก่อนที่จะให้พวกเขาสัมผัสสิ่งสำคัญใดๆ
ลองคิดถึงบทสนทนาที่ไม่ได้อยู่ในไฟล์ใดๆ "เราไม่เรียกเกตเวย์การชำระเงินจากเธรดคำขอ ทุกอย่างผ่านตาราง outbox เพราะเกตเวย์หมดเวลาภายใต้โหลด" "เงินเป็นจำนวนเต็มของหน่วยย่อยเสมอ เราแบนเลขทศนิยมหลังจากเหตุการณ์ปัดเศษ" "ตัวรวม `Account` เป็นเจ้าของ mutatation ยอดคงเหลือ ไม่มีสิ่งอื่นใดเขียนลงในบัญชีแยกประเภท" เหล่านี้คือการตัดสินใจด้านการออกแบบ สิ่งเหล่านี้มองไม่เห็นสำหรับเอเจนต์ที่อ่านซอร์สโค้ด เพราะซอร์สโค้ดแสดง *ผลลัพธ์* ของการตัดสินใจ ไม่ใช่การตัดสินใจหรือเหตุผลเบื้องหลัง เอเจนต์สามารถเห็นว่า `Account.debit()` มีอยู่ มันไม่สามารถเห็นว่าคุณตั้งใจทำให้มันเป็นเส้นทางการเขียนเพียงเส้นทางเดียว ดังนั้นมันจะเพิ่มเส้นทางที่สองอย่างร่าเริง
ข้อตกลงนี้มีรากฐานมาจากการปฏิบัติที่เก่าแก่และเป็นที่ยอมรับอย่างดี รูปแบบ `ARCHITECTURE.md` (ที่ได้รับความนิยมจากงานเขียนที่อ้างอิงอย่างกว้างขวางของ Alex Kladov) แย้งว่า repo ควรมีแผนที่ระดับสูงของฐานโค้ดที่อธิบายโครงสร้างและกฎที่ต้องคงที่เสมอโดยไม่ต้องพยายามซิงค์โค้ดบรรทัดต่อบรรทัด Architecture Decision Records (ADRs) จับการตัดสินใจแต่ละครั้งและเหตุผลเบื้องหลังเมื่อเวลาผ่านไป `DESIGN.md` คือสิ่งที่คุณได้รับเมื่อคุณเขียนเอกสารประเภทนั้น *สำหรับผู้ชมที่รวมถึงเอเจนต์เขียนโค้ด*: สั้น กระชับ เน้นการตัดสินใจ และอยู่ในตำแหน่งที่เอเจนต์จะโหลดมันจริง
คุณสมบัติสองประการที่ทำให้มันใช้งานได้ คือมันอยู่ใน repo ดังนั้นเอเจนต์จึงอ่านมันด้วยเครื่องมือเดียวกับที่มันอ่านโค้ด คุณไม่จำเป็นต้องมีปลั๊กอินหรือการเรียก API และมันเกี่ยวกับเจตนารมณ์ ดังนั้นมันจึงยังคงมีประโยชน์แม้ไฟล์จะย้ายที่ไปมา เปลี่ยนชื่อแพ็คเกจแล้วสกรีนช็อต README ของคุณจะเน่าเสีย แต่กฎ "ตรรกะโดเมนไม่นำเข้าเฟรมเวิร์กเว็บ" ก็ยังคงเป็นจริง
`DESIGN.md` กับ `AGENTS.md` กับ `CLAUDE.md` กับ `README`
ไฟล์เหล่านี้ทับซ้อนกันมากพอที่จะทำให้คนสับสน และแตกต่างกันมากพอที่การรวมเข้าเป็นไฟล์เดียวเป็นความผิดพลาด สรุปสั้นๆ: `README` มีไว้สำหรับคนในการเริ่มต้นใช้งาน `AGENTS.md` เป็นสัญญาการปฏิบัติงานสำหรับเอเจนต์ `CLAUDE.md` เป็นไฟล์คำสั่งเฉพาะสำหรับ Claude และ `DESIGN.md` เป็นเหตุผลทางสถาปัตยกรรมที่ทั้งหมดได้รับประโยชน์จากมัน
`AGENTS.md` เป็นรูปแบบที่แท้จริงและได้รับการยอมรับอย่างกว้างขวางแล้วในตอนนี้ โครงการ agents.md อธิบายว่าเป็น "รูปแบบที่เรียบง่ายและเปิดกว้างสำหรับการนำทางเอเจนต์เขียนโค้ด" ซึ่งใช้กับโครงการหลายหมื่นโครงการและอยู่ภายใต้การดูแลของ Agentic AI Foundation ของ Linux Foundation หน้าที่ของมันคือการปฏิบัติงาน: ขั้นตอนการสร้าง คำสั่งทดสอบ รูปแบบโค้ด ข้อตกลงในการคอมมิต สิ่งที่คุณจะบอกเพื่อนร่วมทีมใหม่เพื่อให้พวกเขาทำงานได้ Per Anthropic's เอกสารหน่วยความจำ Claude Code, `CLAUDE.md` มีบทบาทคำสั่งเดียวกันสำหรับ Claude โดยเฉพาะ เอกสารยังแนะนำว่าหากคุณมี `AGENTS.md` อยู่แล้ว คุณควรสร้าง `CLAUDE.md` ที่นำเข้าด้วย `@AGENTS.md` เพื่อให้เครื่องมือทั้งสองอ่านจากแหล่งความจริงเดียวกัน
สังเกตว่ามีอะไรหายไปจากคำอธิบายเหล่านั้น: เหตุผลทางสถาปัตยกรรมเชิงลึก `AGENTS.md` และ `CLAUDE.md` ถูกปรับให้สั้น เอกสาร Claude Code แนะนำอย่างชัดเจนให้รักษา `CLAUDE.md` ให้ต่ำกว่า 200 บรรทัด เพราะไฟล์ที่ยาวขึ้นจะกินบริบทและลดความน่าเชื่อถือที่โมเดลจะปฏิบัติตาม คำอธิบายสถาปัตยกรรมที่แท้จริง ขอบเขต กฎที่ต้องคงที่เสมอ ทางเลือกที่ถูกปฏิเสธ กฎโมเดลข้อมูล จะไม่พอดีที่นั่นหากไม่ทำให้มันบวม ดังนั้นคุณจึงอ้างอิงถึงมันแทน `DESIGN.md` กลายเป็นเอกสารเชิงลึก; `AGENTS.md` / `CLAUDE.md` ชี้ไปที่มันด้วยบรรทัดเดียว
| ไฟล์ | ผู้ชม | ตอบคำถาม | อายุการใช้งาน / อัตราการเปลี่ยนแปลง | ความยาว |
|---|---|---|---|---|
README.md |
มนุษย์ (ผู้ใช้, ผู้มีส่วนร่วมใหม่) | นี่คืออะไร, ฉันจะเริ่มได้อย่างไร | เปลี่ยนแปลงไปตามฟีเจอร์ | ปานกลาง |
AGENTS.md |
เอเจนต์เขียนโค้ดใดๆ | ฉันจะสร้าง ทดสอบ Lint คอมมิตได้อย่างไรที่นี่ | เปลี่ยนแปลงไปตามเครื่องมือ | สั้น (ปฏิบัติงาน) |
CLAUDE.md |
Claude Code โดยเฉพาะ | เหมือนกับ AGENTS.md, บวกกับกฎเฉพาะของ Claude | เปลี่ยนแปลงไปตามเครื่องมือ | สั้น (ต่ำกว่า ~200 บรรทัด) |
DESIGN.md |
เอเจนต์ + วิศวกร + ผู้รีวิว | ทำไมระบบถึงมีรูปร่างแบบนี้; สิ่งใดห้ามแตกหัก | เปลี่ยนแปลงไปตามสถาปัตยกรรม (ไม่บ่อย) | ปานกลาง, มีการตัดสินใจหนาแน่น |
ความสัมพันธ์เป็นแบบเสริมกัน ไม่ใช่การแข่งขัน การตั้งค่าที่สะอาดตาสำหรับร้านค้า Claude + Codex จะเป็นดังนี้: `README.md` สำหรับมนุษย์; `AGENTS.md` หนึ่งไฟล์พร้อมการสร้าง/ทดสอบ/สไตล์; `CLAUDE.md` ที่เป็นเพียง `@AGENTS.md` บวกสองบรรทัดเฉพาะ Claude; และ `DESIGN.md` ที่เก็บสถาปัตยกรรม เชื่อมโยงจาก `AGENTS.md` เพื่อให้เอเจนต์ทุกตัวโหลดเมื่อจำเป็น ไม่มีการซ้ำซ้อน แต่ละไฟล์มีหน้าที่เดียว หากคุณต้องการดูรายละเอียดเพิ่มเติมเกี่ยวกับการจัดโครงสร้างบริบทของ Claude ในไฟล์เหล่านี้ เวิร์กโฟลว์ Claude Code จะอธิบายโมเดลหน่วยความจำในทางปฏิบัติ
ควรใส่อะไรใน DESIGN.md (พร้อมเทมเพลต)
`DESIGN.md` ควรตอบคำถามที่เอเจนต์ไม่สามารถอนุมานได้จากโค้ด: รูปร่างของระบบ กฎที่ไม่ปรากฏในไฟล์เดียว และการตัดสินใจที่คุณทำโดยเจตนา ให้เขียนแบบประกาศ ทุกส่วนควรอ่านเหมือนกฎที่ผู้รีวิวจะบังคับใช้ ไม่ใช่เรียงความ
ครอบคลุมสิ่งเหล่านี้:
- รูปร่างระบบ: เลเยอร์หรือโมดูล และทิศทางการไหลของ dependencies หนึ่งประโยคต่อขอบเขต
- กฎที่ต้องคงที่เสมอ (Invariants): สิ่งที่ต้องเป็นจริงเสมอ ระบุเป็นสิ่งที่แน่นอน "ยอดคงเหลือไม่เคยติดลบ ยกเว้นเมื่อ OverdraftPolicy ที่ได้รับอนุญาตอนุมัติสำหรับบัญชีนั้น" "การเรียกภายนอกทุกครั้งเป็นแบบ Idempotent โดยใช้ request key"
- การตัดสินใจสำคัญและเหตุผล: ตัวเลือกที่ดูเหมือนจะสุ่มจนกว่าคุณจะรู้ว่าทำไม ใส่ *ทำไม* ลงไปด้วย; เหตุผลคือสิ่งที่หยุดเอเจนต์จากการ "แก้ไข" มัน
- ทางเลือกที่ถูกปฏิเสธ: สิ่งที่คุณจงใจ *ไม่* ทำ เพื่อที่เอเจนต์จะได้ไม่เสนอเป็นแนวคิดใหม่ ส่วนนี้เพียงส่วนเดียวจะป้องกันข้อเสนอแนะที่ไม่ดีจำนวนมาก
- กฎข้อมูลและโดเมน: การแทนค่าเงิน การจัดการเวลา/โซนเวลา ตัวระบุ การลบแบบซอฟต์ (soft-delete) การรองรับหลายผู้เช่า (multi-tenancy)
- แหล่งความจริงของสัญญา API: ที่อยู่ของ OpenAPI spec และกฎที่ระบุว่ามันเป็นผู้มีอำนาจเหนือประเภทที่เขียนด้วยมือ
- โค้ดใหม่จะไปที่ไหน: แผนที่สั้นๆ ที่บอกว่า "หากคุณกำลังเพิ่ม X มันควรอยู่ใน Y" เพื่อให้เอเจนต์หยุดกระจายตรรกะ
- อยู่นอกขอบเขต / ห้ามแตะ: ไฟล์ที่สร้างขึ้น โมดูลเก่าที่กำลังย้ายถิ่นฐาน สิ่งใดก็ตามที่เอเจนต์ควรปล่อยไว้
นี่คือเทมเพลตฉบับเต็ม เขียนขึ้นสำหรับบริการ API การชำระเงินที่สมจริง คัดลอกไป ลบสิ่งที่ไม่เกี่ยวข้องออก แล้วเติมส่วนที่เหลือ
# DESIGN.md: บริการ Payments API
ไฟล์นี้บันทึกเจตนารมณ์ทางสถาปัตยกรรมและการตัดสินใจเบื้องหลัง
โปรดอ่านสิ่งนี้ก่อนที่จะสร้างหรือแก้ไขโค้ด หากการเปลี่ยนแปลงขัดแย้ง
กับกฎที่ระบุไว้ที่นี่ ให้หยุดและแจ้งปัญหาแทนที่จะหลีกเลี่ยง
## รูปร่างระบบ
แบ่งเป็นชั้น, Dependencies ชี้เข้าด้านในเท่านั้น:
http (handlers, DTOs) -> app (use cases) -> domain (entities,
invariants) <- infra (db, gateway clients)
- `domain/` ไม่มีการนำเข้าจาก `http/`, `app/` หรือเฟรมเวิร์กใดๆ
- `infra/` ใช้ Interface ที่ประกาศใน `domain/` หรือ `app/`
- `http/` ไม่เคยเข้าถึงฐานข้อมูลหรือเกตเวย์การชำระเงินโดยตรง
มันเรียก Use Case ใน `app/`
## กฎที่ต้องคงที่เสมอ (Invariants) (ต้องเป็นจริงเสมอ)
- รายการในบัญชีแยกประเภทไม่สามารถเปลี่ยนแปลงได้เมื่อถูกเขียนแล้ว การแก้ไขคือรายการใหม่
ที่เป็นการชดเชย ไม่ใช่การอัปเดตหรือลบ
- ยอดคงเหลือของบัญชีมาจากรายการในบัญชีแยกประเภท ไม่ได้ถูกจัดเก็บเป็นฟิลด์ที่เปลี่ยนแปลงได้
ซึ่งโค้ดสามารถตั้งค่าได้โดยตรง
- เงินคือจำนวนเต็มของหน่วยย่อย (เซ็นต์) บวกกับรหัสสกุลเงิน ISO-4217
ห้ามใช้เลขทศนิยม ห้ามผสมสกุลเงินในการดำเนินการเดียว
- การเรียกเกตเวย์การชำระเงินภายนอกทุกครั้งต้องเป็น Idempotent โดยใช้
`idempotency_key` การลองใหม่ต้องไม่คิดเงินซ้ำ
- ยอดคงเหลือไม่เคยติดลบ ยกเว้นเมื่อ `OverdraftPolicy` ที่ชัดเจน
อนุญาตสำหรับบัญชีนั้น
## การตัดสินใจสำคัญและเหตุผล
- **รูปแบบ Outbox สำหรับการเรียกเกตเวย์** Handlers เขียนแถวเจตนา
ในธุรกรรมฐานข้อมูลเดียวกันกับการเปลี่ยนแปลงทางธุรกิจ จากนั้น Worker
จะเรียกเกตเวย์ เหตุผล: เกตเวย์หมดเวลาภายใต้โหลด การทำแบบ In-line
ทำให้ Request Latency และการจัดการข้อผิดพลาดไม่ได้รับการดูแล
ห้ามเรียกเกตเวย์จาก Request Handler
- **เส้นทางการเขียนเดียวต่อ Aggregate** เฉพาะ `Account.post_entry()`
เท่านั้นที่เขียนลงในบัญชีแยกประเภท เหตุผล: เส้นทางการเขียนที่สองทำให้เกิด
ยอดคงเหลือไม่ตรงกันในเดือนมีนาคม 2025 เพิ่มพฤติกรรมใหม่เป็นเมธอดบน
Aggregate ไม่ใช่ Query ใหม่
- **Event Sourcing สำหรับบัญชีแยกประเภทเท่านั้น** ระบบส่วนที่เหลือเป็น
CRUD เหตุผล: เราต้องการ Audit Trail ที่สมบูรณ์แบบสำหรับเงินและ
ไม่มีอะไรอื่น และ Event Sourcing เต็มรูปแบบมีค่าใช้จ่ายสูงเกินไปในที่อื่น
## ทางเลือกที่ถูกปฏิเสธ (ห้ามนำกลับมาใช้ใหม่)
- ORM Lazy-loading ข้าม Aggregates; ทำให้เกิด N+1 และขอบเขตธุรกรรมไม่ชัดเจน
Repositories จะคืน Aggregates ที่โหลดมาทั้งหมด
- การจัดเก็บยอดคงเหลือเป็นคอลัมน์ที่อัปเดตโดยตรง; ดูเหตุการณ์ยอดคงเหลือไม่ตรงกัน
ยอดคงเหลือจะถูกคำนวณเสมอ
- ไลบรารี `Money` ทั่วไปที่ดึงมาจาก Registry; เรามี `domain/money.py` ของเราเอง;
จงใช้มัน
- Webhooks แบบ Synchronous ไปยัง Merchants จาก Request Thread; พวกมัน
บล็อกและล้มเหลวอย่างเงียบๆ ใช้ Notification Queue แทน
## กฎข้อมูลและโดเมน
- Timestamps ทั้งหมดเป็น UTC, จัดเก็บเป็น timestamptz, จัดรูปแบบ RFC 3339
ที่ Edge ห้าม Datetime ที่ไม่มีโซนเวลาข้ามขอบเขตฟังก์ชัน
- ID เป็น ULID ที่สร้างใน App Layer, ไม่ใช่ DB Autoincrement
- ไม่มีการใช้ Soft Delete บันทึกจะ Active หรือย้ายไปยัง Archive Table
โดย Use Case ที่ชัดเจน
- Multi-tenant: ทุก Query จะถูกกำหนดขอบเขตด้วย `tenant_id` เมธอด Repository
ที่ไม่มี Tenant Scope ถือเป็น Bug
## แหล่งความจริงของสัญญา API
- OpenAPI 3.1 Spec ใน `api/openapi.yaml` ถือเป็นข้อมูลที่เชื่อถือได้
ประเภท Request/Response ถูกสร้างขึ้นจากมัน; ห้ามแก้ไขด้วยมือ
ประเภทที่สร้างขึ้นใน `http/generated/`
- Endpoint ใหม่หรือที่เปลี่ยนแปลง: อัปเดต `api/openapi.yaml` ก่อน จากนั้น
สร้างใหม่ แล้วจึง implement Spec ถูกออกแบบและรีวิวใน
Apidog ก่อนการเปลี่ยนแปลงโค้ด
- Error Response เป็นไปตาม RFC 9457 (problem+json) ใช้ตัวช่วย `problem()`
ที่ใช้ร่วมกัน; ห้ามสร้างรูปแบบข้อผิดพลาดเฉพาะกิจ
## โค้ดใหม่จะไปที่ไหน
- Endpoint ใหม่: Route ใน `http/routes/`, DTO ใน `http/dto/`, Use Case ใน
`app/usecases/`, Domain Logic ใน `domain/`
- การรวมระบบภายนอกใหม่: Client ใน `infra/clients/`, Interface
ใน `app/ports/`
- Cross-cutting Concern (Auth, Logging, Idempotency): Middleware ใน
`http/middleware/`, ห้าม In-line ใน Handlers
## อยู่นอกขอบเขต / ห้ามแตะ
- `http/generated/`: สร้างใหม่จาก OpenAPI, การแก้ไขจะหายไป
- `legacy/billing_v1/`: ถูกตรึง, อยู่ระหว่างการย้ายถิ่นฐาน ห้ามขยาย
- `migrations/`: ห้ามแก้ไข Migration ที่ถูกนำไปใช้แล้ว; เพิ่ม Migration ใหม่
## เมื่อมีข้อสงสัย
หากการเปลี่ยนแปลงที่ร้องขอจำเป็นต้องละเมิดกฎข้างต้น สิ่งที่ถูกต้องคือ
การแจ้งและเสนอทางเลือกที่สอดคล้องกับการออกแบบที่เล็กที่สุด
ไม่ใช่การหลีกเลี่ยงกฎอย่างเงียบๆ
ส่วนสุดท้ายนั้นสำคัญกว่าที่เห็น การบอกเอเจนต์ว่าควรทำอย่างไรเมื่อคำขอขัดแย้งกับการออกแบบ จะเปลี่ยนไฟล์จากเอกสารเชิงรับเป็นรั้วกั้นเชิงรุก หากไม่มีสิ่งนี้ เอเจนต์ที่เจอข้อจำกัดมักจะเลี่ยงไปและส่งวิธีแก้ปัญหานั้น
เอเจนต์เขียนโค้ดใช้ `DESIGN.md` อย่างไร
เอเจนต์ไม่มี Parser เฉพาะสำหรับ `DESIGN.md` พวกมันใช้มันในลักษณะเดียวกับที่พวกมันใช้ไฟล์อื่นๆ: โดยการอ่านมันด้วยเครื่องมือไฟล์ของพวกมัน และถือว่าเนื้อหาเป็นบริบท ดังนั้นกลไกในการโหลดจึงสำคัญ และมันจะแตกต่างกันเล็กน้อยในแต่ละเครื่องมือ
รูปแบบที่เชื่อถือได้คือการอ้างอิง `DESIGN.md` จากไฟล์คำสั่งที่เอเจนต์แต่ละตัวโหลดอยู่แล้วเมื่อเริ่มต้น สำหรับ Claude Code นั่นคือ `CLAUDE.md` เอกสารหน่วยความจำอธิบายไวยากรณ์การนำเข้า `@path` โดยที่ `@DESIGN.md` จะขยายไฟล์เข้าสู่บริบทเมื่อเซสชันเริ่มต้น สำหรับระบบนิเวศ `AGENTS.md` คุณสามารถเพิ่มบรรทัดใน `AGENTS.md` ที่ชี้ไปที่มัน ("กฎสถาปัตยกรรมและการออกแบบ: ดู `DESIGN.md`; อ่านก่อนการเปลี่ยนแปลงโครงสร้าง") เอเจนต์ที่เดินสำรวจโครงสร้างไดเรกทอรีจะหยิบ `AGENTS.md` ที่อยู่ใกล้ที่สุด เห็นตัวชี้ และดึง `DESIGN.md` เมื่อการทำงานแตะต้องสถาปัตยกรรม ไม่ว่าวิธีใด คุณก็ไม่ต้องทำซ้ำเนื้อหา คุณกำลังรักษาไฟล์การปฏิบัติงานที่สั้นให้สั้น และปล่อยให้ไฟล์เชิงลึกเป็นเชิงลึก
ข้อสังเกตเชิงปฏิบัติสามข้อจากพฤติกรรมของเครื่องมือเหล่านี้:
ประการแรก เอเจนต์ถือว่าไฟล์เป็นบริบท ไม่ใช่กฎที่บังคับใช้ เอกสาร Claude Code ระบุอย่างตรงไปตรงมาว่าเนื้อหา `CLAUDE.md` เป็นแนวทางที่โมเดลพยายามปฏิบัติตาม ไม่ใช่ข้อจำกัดที่เข้มงวด เช่นเดียวกับสิ่งที่คุณอ้างอิงจากมัน นั่นคือเหตุผลที่เทมเพลตใช้ถ้อยคำทุกอย่างเป็นสิ่งแน่นอนที่ทดสอบได้ และเพิ่มคำสั่ง "เมื่อมีข้อสงสัย" ที่ชัดเจน; ถ้อยคำคลุมเครือจะถูกละเลยภายใต้แรงกดดัน กฎที่คมชัดจะถูกปฏิบัติตามบ่อยกว่า
ประการที่สอง ความยาวและโครงสร้างเปลี่ยนแปลงการปฏิบัติตาม หัวเรื่องและสัญลักษณ์แสดงหัวข้อย่อยดีกว่าย่อหน้า เพราะโมเดลจะสแกนโครงสร้างเหมือนที่ผู้อ่านทำ กำแพงปรัชญาสถาปัตยกรรม 3 หน้าจะถูกอ่านผ่านๆ; กฎที่ต้องคงที่เสมอที่ชัดเจนสิบข้อภายใต้หัวเรื่องที่ชัดเจนจะถูกนำมาใช้ เขียนเพื่อการดึงข้อมูล ไม่ใช่เพื่อร้อยแก้ว
ประการที่สาม ไฟล์เปลี่ยนเศรษฐศาสตร์การรีวิว ไม่ใช่แค่การสร้าง แม้ว่าเอเจนต์จะละเลยมันบางส่วน ผู้รีวิวก็สามารถชี้ไปที่กฎที่ถูกละเมิด และเอเจนต์จะแก้ไขได้ในการวนซ้ำเดียว ("สิ่งนี้ละเมิดกฎ single-write-path ใน `DESIGN.md`") วงจรตอบรับนั้น การปรับปรุงที่อิงกับการตัดสินใจที่เขียนไว้ คือจุดที่มูลค่าที่แท้จริงจำนวนมากเกิดขึ้น ทีมที่สร้าง Agent Harnesses ของตัวเองพึ่งพาสิ่งนี้อย่างแม่นยำ; ดู สร้าง Claude Code ของคุณเอง สำหรับวิธีการเชื่อมโยงวงจรนั้นเข้ากับเวิร์กโฟลว์อัตโนมัติ
Anti-patterns และการรักษามันไม่ให้เสื่อมสภาพ
วิธีที่เร็วที่สุดที่จะทำให้ `DESIGN.md` ไม่มีค่าคือการเขียนมันเหมือนหน้า wiki ไฟล์การออกแบบที่เสื่อมสภาพนั้นแย่กว่าการไม่มีเลย เพราะทั้งเอเจนต์และมนุษย์ต่างก็เชื่อมันและถูกเข้าใจผิด หลีกเลี่ยงสิ่งเหล่านี้
การกล่าวซ้ำโค้ด "คลาส `UserService` จัดการผู้ใช้" ไม่ได้บอกเอเจนต์สิ่งใดที่มันไม่สามารถอ่านได้จาก `user_service.py` หากประโยคเป็นจริงโดยการอ่านไฟล์ ให้ตัดทิ้งไป เก็บเฉพาะสิ่งที่ไม่สามารถบอกได้จากโค้ด: เหตุผล กฎที่ต้องคงที่เสมอ เส้นทางที่ถูกปฏิเสธ
การแทรกบทช่วยสอนมากเกินไป บทช่วยสอน "วิธีเพิ่มฟีเจอร์" ทีละขั้นตอนควรอยู่ใน `CONTRIBUTING.md` หรือทักษะ ไม่ใช่อยู่ที่นี่ ทันทีที่ `DESIGN.md` มีคำสั่ง shell และโค้ดที่คัดลอกวาง มันคือเอกสารที่ไม่ถูกต้อง และมันจะล้าสมัยไปตามความเร็วของเครื่องมือ
ความปรารถนาเป็นความจริง การเขียนว่า "ระบบใช้ CQRS" ในขณะที่ครึ่งหนึ่งของมันไม่ได้ใช้ เป็นการฝึกเอเจนต์ให้สร้างโค้ดที่ตรงกับเรื่องแต่ง บันทึกสิ่งที่จริงในตอนนี้บวกกับสิ่งที่คุณกำลังมุ่งไปโดยเจตนา และระบุความแตกต่าง "เป้าหมาย: การเขียนทั้งหมดผ่าน Use Cases ปัจจุบัน: `legacy/` ข้ามสิ่งนี้; ห้ามขยายมัน"
ไม่มีเจ้าของ, ไม่มีตัวกระตุ้นการรีวิว ไฟล์การออกแบบที่ไม่มีใครรับผิดชอบจะเสื่อมสภาพในหนึ่งไตรมาส ผูกมันเข้ากับตัวกระตุ้น: รีวิว `DESIGN.md` ใน PR ใดๆ ที่เพิ่มโมดูล เปลี่ยนขอบเขตเลเยอร์ หรือแนะนำ dependency ภายนอกใหม่ ใส่กฎนั้นลงในเทมเพลต PR บางทีมเพิ่มรายการตรวจสอบ "การเปลี่ยนแปลงนี้เปลี่ยนการตัดสินใจใน `DESIGN.md` หรือไม่? ถ้าใช่ ให้อัปเดตใน PR เดียวกัน"
โรงละครการซิงโครไนซ์ อย่าพยายามซิงค์มันทีละบรรทัดกับโค้ด นั่นเป็นเกมที่แพ้ และเป็นเหตุผลที่เอกสารสถาปัตยกรรมถูกทอดทิ้ง รักษามันในระดับของการตัดสินใจที่เปลี่ยนไม่กี่ครั้งต่อปี ไม่ใช่ Signature ของฟังก์ชันที่เปลี่ยนทุกสัปดาห์ คำแนะนำ `ARCHITECTURE.md` ของ matklad คือสัญชาตญาณที่ถูกต้องในที่นี้: เขียนเฉพาะสิ่งที่ไม่น่าจะเปลี่ยนแปลงบ่อยๆ
ขัดแย้งกับไฟล์คำสั่งอื่นๆ หาก `AGENTS.md` พูดถึงการจัดการข้อผิดพลาดอย่างหนึ่ง และ `DESIGN.md` พูดอีกอย่างหนึ่ง เอเจนต์จะเลือกอย่างใดอย่างหนึ่งแบบสุ่ม รักษากฎการปฏิบัติงานใน `AGENTS.md` / `CLAUDE.md` และกฎสถาปัตยกรรมใน `DESIGN.md` และอย่าให้ทับซ้อนกัน เมื่อจำเป็นต้องอ้างอิงถึงกัน ไฟล์หนึ่งจะชี้ไปยังอีกไฟล์หนึ่ง; พวกมันไม่ได้ยืนยันความจริงเดียวกันทั้งสองไฟล์
`DESIGN.md` ที่มีสุขภาพดีนั้นสั้น กระชับ เป็นแบบประกาศ มีเจ้าของ และได้รับการรีวิวเมื่อมีตัวกระตุ้น หากของคุณยาว เป็นเรื่องเล่า และแก้ไขครั้งสุดท้ายเมื่อปีที่แล้ว เอเจนต์กำลังอ่านเรื่องแต่ง
`DESIGN.md` สำหรับฐานโค้ด API และแบ็กเอนด์
นี่คือจุดที่ไฟล์นี้มีประโยชน์อย่างมาก บริการ API และแบ็กเอนด์มีข้อจำกัดที่มองไม่เห็นและมีต้นทุนสูง ซึ่งเอเจนต์ทำได้แย่ที่สุด: ขอบเขตสัญญา ความหมายของธุรกรรม Idempotency ความสมบูรณ์ของข้อมูล การแบ่งชั้น ไม่มีสิ่งใดที่ชัดเจนจากไฟล์เดียว และการทำผิดจะส่งผลให้เกิด Bug ที่ไปถึง Production และเงิน
ใส่สิ่งเฉพาะ API เหล่านี้ใน `DESIGN.md` แล้วคุณภาพของผลลัพธ์ของเอเจนต์ในตั๋วงานแบ็กเอนด์จะก้าวกระโดด:
สัญญาคือแหล่งความจริง และระบุว่ามันอยู่ที่ไหน ระบุอย่างชัดเจนว่า OpenAPI spec เป็นผู้มีอำนาจ และประเภทที่สร้างขึ้นไม่ควรแก้ไขด้วยมือ เอเจนต์ชอบที่จะ "ช่วย" ปรับแต่งประเภทที่สร้างขึ้นเพื่อให้ Build ผ่าน; เพียงหนึ่งบรรทัดใน `DESIGN.md` จะหยุดสิ่งนั้นได้ ชี้ไปที่เส้นทางไฟล์ Spec หากคุณออกแบบสัญญาก่อนใน Apidog และส่งออกเอกสาร OpenAPI เข้าสู่ repo `DESIGN.md` ของคุณสามารถตั้งชื่อไฟล์นั้นเป็นสิ่งที่ทุก Endpoint ต้องปฏิบัติตาม และเอเจนต์ก็จะมีเป้าหมายที่ชัดเจน เหตุผลในการออกแบบสัญญาก่อนโค้ดได้กล่าวถึงใน การออกแบบ API สำหรับเอเจนต์ AI; สัญญาแบบ Design-first คือสิ่งที่ทำให้ Handlers ที่สร้างโดยเอเจนต์ปลอดภัยที่จะยอมรับ
ขอบเขตของธุรกรรมและความสอดคล้อง ธุรกรรมเริ่มต้นและสิ้นสุดที่ใด? อนุญาตให้ทำอะไรภายในนั้นบ้าง? "การเรียกภายนอกไม่เคยเกิดขึ้นภายในธุรกรรม DB; ใช้ Outbox" เอเจนต์จะเรียก In-line ที่ไร้เดียงสาเสมอ เว้นแต่ไฟล์จะห้าม
Idempotency และการลองใหม่ ระบุกลยุทธ์ Idempotency เป็น Invariant Endpoint สำหรับการชำระเงิน คำสั่งซื้อ และการจัดเตรียม เป็นจุดที่ Key Idempotency ที่ขาดหายไปจะกลายเป็นการคิดเงินซ้ำ เอเจนต์จะไม่อนุมานสิ่งนี้จากการอ่าน Handler
โมเดลข้อผิดพลาด หนึ่งประโยค: "ข้อผิดพลาดทั้งหมดเป็น problem+json ผ่านตัวช่วย `problem()`; ห้ามสร้างรูปแบบข้อผิดพลาดเฉพาะกิจ" หากไม่มีสิ่งนี้ คุณจะได้ Error Envelope ที่แตกต่างกันไปในแต่ละ Endpoint ซึ่งทำให้ Client ทุกตัวเสียหาย
ขอบเขต Auth และ Tenancy "ทุก Query ถูกกำหนดขอบเขตด้วย Tenant ID; เมธอด Repository ที่ไม่มี Tenant Scope ถือเป็น Bug" นี่คือกฎความปลอดภัยที่ต้องคงที่เสมอ และมันมองไม่เห็นใน Query แต่ละรายการ ดังนั้นมันจึงเป็นกฎประเภทหนึ่งที่ต้องเขียนลงไป
กฎการกำหนดเวอร์ชันและการเปลี่ยนแปลงที่มีผลกระทบ (Breaking Change) อะไรคือสิ่งที่ถือว่าเป็นการเปลี่ยนแปลงที่มีผลกระทบ, วิธีการกำหนดเวอร์ชัน, อะไรที่อนุญาตในการเปลี่ยนแปลงเล็กน้อย เอเจนต์จะเปลี่ยนชื่อฟิลด์ Response อย่างร่าเริง; ไฟล์จะบอกพวกเขาว่านั่นคือการเปลี่ยนแปลงที่มีผลกระทบพร้อมขั้นตอนการดำเนินการ
สำหรับงานแบ็กเอนด์ ผลลัพธ์ที่ได้นั้นเป็นรูปธรรม: การละเมิดเลเยอร์น้อยลง ไม่มีการเรียกเกตเวย์ In-line ที่น่าประหลาดใจ รูปแบบข้อผิดพลาดและการแบ่งหน้าเว็บที่สอดคล้องกัน และ Handlers ที่เป็นไปตามสัญญา เพราะเอเจนต์ถูกชี้ไปที่ OpenAPI spec แทนที่จะคาดเดา Schema เอเจนต์จะหยุดสร้างสรรค์และเริ่มปฏิบัติตาม หากคุณต้องการให้เอเจนต์ทดสอบ API ที่เพิ่งเขียนด้วย การรวมสัญญาเข้ากับการออกแบบคือสิ่งที่ช่วยให้เครื่องมือและเอเจนต์สามารถทดสอบกับ Interface ที่รู้จักได้ ดาวน์โหลด Apidog ช่วยให้คุณมีพื้นที่ทำงานแบบ Design-first การส่งออก OpenAPI ที่ `DESIGN.md` ของคุณชี้ไป และเซิร์ฟเวอร์ MCP และ Debugger ของเอเจนต์ AI สำหรับการตรวจสอบว่า Endpoint ที่สร้างขึ้นตรงกับสัญญาจริงหรือไม่
สรุป
- `DESIGN.md` บันทึกว่า *ทำไม* โค้ดของคุณถึงมีรูปร่างแบบนั้น: กฎที่ต้องคงที่เสมอ การตัดสินใจ และทางเลือกที่ถูกปฏิเสธที่เอเจนต์ไม่สามารถอ่านได้จากซอร์สโค้ด
- มันเสริมและไม่แทนที่ `AGENTS.md` และ `CLAUDE.md`: ไฟล์เหล่านั้นยังคงสั้นและเกี่ยวกับการปฏิบัติงาน; `DESIGN.md` เก็บสถาปัตยกรรมเชิงลึกและถูกอ้างอิงจากไฟล์เหล่านั้น
- เขียนมันในรูปแบบประกาศ เป็นสิ่งแน่นอนที่ทดสอบได้บวกกับกฎ "เมื่อมีข้อสงสัย ให้แจ้งปัญหา อย่าหลีกเลี่ยง" เพื่อให้มันทำหน้าที่เป็นรั้วกั้น ไม่ใช่ร้อยแก้วเชิงรับ
- มันให้ผลตอบแทนสูงสุดกับฐานโค้ด API และแบ็กเอนด์ ที่ขอบเขตสัญญา ธุรกรรม Idempotency และขอบเขต Tenancy มองไม่เห็นและมีค่าใช้จ่ายสูงหากทำผิดพลาด
- รักษามันไม่ให้เสื่อมสภาพด้วยเจ้าของและตัวกระตุ้นการรีวิวที่ผูกกับเทมเพลต PR ของคุณ; ห้ามซิงค์มันทีละบรรทัดกับโค้ด
- ประโยชน์สูงสุดสำหรับแบ็กเอนด์คือการระบุว่า OpenAPI spec เป็นผู้มีอำนาจ เพื่อให้เอเจนต์ปฏิบัติตามสัญญาแทนที่จะสร้าง Schema หรือแก้ไขประเภทที่สร้างขึ้นด้วยมือ
- ออกแบบสัญญาเป็นอันดับแรก ดาวน์โหลด Apidog เพื่อออกแบบ API แบบ Design-first ส่งออก OpenAPI spec ที่ `DESIGN.md` ของคุณชี้ไป และทดสอบว่า Endpoint ที่สร้างโดยเอเจนต์ตรงกับมันจริงหรือไม่
คำถามที่พบบ่อย
`DESIGN.md` เป็นมาตรฐานอย่างเป็นทางการเหมือน `AGENTS.md` หรือไม่?
ไม่ใช่ `AGENTS.md` เป็นรูปแบบที่กำหนดและได้รับการยอมรับอย่างกว้างขวางแล้ว ซึ่งตอนนี้อยู่ภายใต้การดูแลของ Agentic AI Foundation ของ Linux Foundation `DESIGN.md` เป็นข้อตกลงของชุมชนที่ไม่มีเจ้าของหรือ Spec เดียวกัน ในตระกูลเดียวกับ `ARCHITECTURE.md` และ ADRs ถือว่ามันเป็นรูปแบบที่มีประโยชน์ที่คุณสามารถปรับใช้ได้ ไม่ใช่มาตรฐานที่คุณต้องปฏิบัติตาม
ฉันจำเป็นต้องมี `DESIGN.md` หรือไม่ หากฉันมี `AGENTS.md` หรือ `CLAUDE.md` อยู่แล้ว?
หากสถาปัตยกรรมของคุณมีข้อจำกัดที่ไม่ชัดเจน ใช่ `AGENTS.md` และ `CLAUDE.md` มีวัตถุประสงค์เพื่อให้สั้นและเกี่ยวกับการปฏิบัติงาน; เอกสาร Claude Code แนะนำให้รักษา `CLAUDE.md` ให้ต่ำกว่าประมาณ 200 บรรทัด เหตุผลทางสถาปัตยกรรมเชิงลึกไม่พอดีที่นั่นหากไม่ทำให้มันบวมและลดการปฏิบัติตาม ดังนั้นคุณจึงใส่มันใน `DESIGN.md` และอ้างอิงถึงมัน สำหรับไฟล์การปฏิบัติงานเอง โปรดดู วิธีการเขียนไฟล์ AGENTS.md
`DESIGN.md` แตกต่างจาก `ARCHITECTURE.md` อย่างไร?
ส่วนใหญ่เป็นเรื่องของเจตนารมณ์และผู้ชม `ARCHITECTURE.md` เป็นข้อตกลงที่เก่ากว่าซึ่งมุ่งเป้าไปที่ผู้มีส่วนร่วมที่เป็นมนุษย์ในการทำแผนที่ฐานโค้ด `DESIGN.md` เป็นแนวคิดเดียวกันที่เขียนขึ้นสำหรับผู้ชมที่รวมถึงเอเจนต์เขียนโค้ด: เป็นแบบประกาศมากขึ้น เน้นการตัดสินใจและกฎที่ต้องคงที่เสมอ และถูกอ้างอิงอย่างชัดเจนจากไฟล์คำสั่งของเอเจนต์เพื่อให้มันถูกโหลดเข้าสู่บริบท หลายทีมใช้ไฟล์เดียวและชื่อเดียว; หลักการก็เหมือนกัน
`DESIGN.md` ควรสั้นแค่ไหน?
ยาวพอที่จะครอบคลุมการตัดสินใจที่เอเจนต์ทำผิดพลาดอยู่เสมอ สั้นพอที่ทุกบรรทัดมีค่า การมีส่วนของการตัดสินใจที่หนาแน่นดีกว่าการครอบคลุมทั้งหมด หากอ่านเหมือนบทช่วยสอนหรือกล่าวซ้ำโค้ด ให้ตัดทิ้งไป กฎที่ต้องคงที่เสมอและเหตุผลที่กระชับสองถึงสี่หน้าดีกว่าเรื่องเล่าสิบห้าหน้าที่เอเจนต์จะไม่อ่านอย่างละเอียด
ฉันจะทำให้เอเจนต์อ่านมันได้อย่างไร?
อ้างอิงถึงมันจากไฟล์ที่เอเจนต์โหลดอยู่แล้วเมื่อเริ่มต้น สำหรับ Claude Code ให้นำเข้าจาก `CLAUDE.md` ด้วย `@DESIGN.md` สำหรับระบบนิเวศ `AGENTS.md` ให้เพิ่มบรรทัดตัวชี้ใน `AGENTS.md` ที่บอกเอเจนต์ให้อ่าน `DESIGN.md` ก่อนการเปลี่ยนแปลงโครงสร้าง อย่าคัดลอกทั้งหมดลงในไฟล์สั้นๆ; ให้อ้างอิงถึงมันเพื่อให้ไฟล์การปฏิบัติงานยังคงสั้น
เอเจนต์จะปฏิบัติตาม `DESIGN.md` เสมอไปหรือไม่?
ไม่ และคุณควรออกแบบโดยคำนึงถึงสิ่งนั้น ไฟล์คำสั่งของเอเจนต์เป็นบริบทที่โมเดลพยายามปฏิบัติตาม ไม่ใช่การกำหนดค่าที่บังคับใช้ เขียนกฎให้เป็นสิ่งแน่นอนที่ชัดเจน เพิ่มคำสั่ง "แจ้งข้อขัดแย้ง อย่าหลีกเลี่ยง" ที่ชัดเจน และพึ่งพาวงจรการรีวิว; การชี้ไปที่กฎที่ถูกละเมิดใน `DESIGN.md` จะได้รับการแก้ไขที่รวดเร็วและถูกต้องแม้ว่าการประมวลผลครั้งแรกจะพลาดไปก็ตาม
`DESIGN.md` ช่วยแก้ปัญหาสัญญา API โดยเฉพาะหรือไม่?
ช่วยได้มาก การใช้งานที่มีคุณค่าสูงสุดสำหรับแบ็กเอนด์คือการระบุว่า OpenAPI spec เป็นผู้มีอำนาจและระบุชื่อไฟล์ เพื่อให้เอเจนต์ปฏิบัติตามสัญญาแทนที่จะสร้าง Schema หรือแก้ไขประเภทที่สร้างขึ้นด้วยมือ การออกแบบสัญญาเป็นอันดับแรกในเครื่องมืออย่าง Apidog จะให้เป้าหมายที่ชัดเจนแก่เอเจนต์ที่ `DESIGN.md` ของคุณสามารถชี้ไปได้โดยตรง
`DESIGN.md` ควรกำหนดไว้ที่ใดใน Repo?
ที่ Root ของ Repository ถัดจาก `README.md` และ `AGENTS.md` เพื่อให้เอเจนต์และมนุษย์พบได้โดยไม่ต้องค้นหา ใน Monorepo, `DESIGN.md` ที่ Root สำหรับกฎทั่วทั้งระบบบวกกับ `DESIGN.md` ต่อแพ็คเกจสำหรับสถาปัตยกรรมภายในจะทำงานได้ดี สะท้อนถึงวิธีการที่เอเจนต์อ่าน `AGENTS.md` ที่อยู่ใกล้ที่สุดในโครงสร้างไดเรกทอรี
