Skip to content
Tayakorn

Harness Engineering Handbook

LEVEL 4 · แงะของจริง

ผ่า Claude Code — case study เต็มตัว

สิบเอ็ดบทที่ผ่านมาคือทฤษฎี — บทนี้เอาของจริงที่พี่ใช้อยู่ทุกวันขึ้นโต๊ะผ่า แล้วชี้ว่าอวัยวะแต่ละชิ้นคือบทไหนของเล่มนี้ · เป้าหมายไม่ใช่รู้จัก Claude Code ดีขึ้น (พี่ใช้เป็นอยู่แล้ว) แต่คือเห็นการตัดสินใจออกแบบว่าแต่ละอย่างตอบ trade-off ที่เราเรียนมายังไง

CLAUDE CODE — วางบนโครง 5 ชิ้นจากบทที่ 1CONTEXTบท 3 · 9CLAUDE.mdโหลดทุกเซสชันagentic searchค้นเอง ไม่ index ก่อนauto-compactionสรุปเมื่อใกล้เต็มskillsโหลดตามงานTOOLSบท 4 · 10Read·Edit·Writeไฟล์ทุกภาษาGlob·Grepมือค้นของ agentBashประตูเปิดโลกsubagentงานรกโยนให้ลูกFEEDBACKบท 5เทสต์ผ่าน Bashเห็น error ดิบเต็ม ๆlint·typecheckกติกาอัตโนมัติscreenshotงาน UI เห็นภาพจริงexit codeรู้ผลทุกคำสั่งGUARDRAILSบท 6permission modesหลายระดับอิสระallowlistจำคำตอบเดิมhooksเบรกที่เป็นโค้ดsandbox·worktreeกรงวิ่งเต็มสปีดOBSERVABILITYบท 7 · 8narration สดเล่าว่ากำลังทำอะไรtranscriptย้อนดูได้ทุกรอบ/usagetoken · เงิน · เวลาเพดานรอบ/งบกัน doom loopทุกช่องคือคำตอบของ trade-off ที่บทก่อนหน้าตั้งไว้ — ไม่มีชิ้นไหนอยู่ตรงนั้นโดยบังเอิญ
FIG 12.1 anatomy ของ Claude Code บนโครง 5 ชิ้น · ลองผ่า harness ตัวอื่นที่พี่ใช้ดูจะพบว่าลงโครงเดียวกันได้เสมอ

12.1 การตัดสินใจที่น่าผ่าที่สุดสามเรื่อง

เรื่องแรก: ทำไม tools มีไม่กี่ตัว และไม่มี tool เฉพาะภาษา · ไม่มี refactor_python ไม่มี fix_css — มีแต่ Read/Edit/Bash/Glob/Grep ที่ compose กันได้ครอบงานแทบทุกแบบ · นี่คือหลัก "จำนวนพอดี" ของบทที่ 4 แบบสุดทาง: เครื่องมือ general ไม่กี่ตัว = schema ไม่ท่วม context ไม่มีตัวเลือกให้สับสน · ราคาที่จ่าย: Bash กลายเป็นประตูที่เปิดได้ทุกอย่าง — จึงเป็นจุดที่ permission ต้องละเอียดที่สุด (บทที่ 6) · เห็นไหมครับ การตัดสินใจฝั่ง tools ลากการตัดสินใจฝั่ง guardrails ตามมา — ชิ้นส่วน 5 ชิ้นไม่เคยออกแบบแยกกันได้

เรื่องที่สอง: ทำไมไม่ index repo ล่วงหน้า · หลายระบบยุคก่อนสร้าง index/embedding ของทั้ง repo ไว้ค้น — Claude Code เลือกทางตรงข้าม: ให้ agent ใช้ Glob/Grep ค้นสด ๆ เอาตอนต้องใช้ · คือ just-in-time retrieval (บทที่ 3) เต็มรูปแบบ: ไม่มี index ที่ stale ไม่ต้อง sync และ agent ตัดสินใจเองว่าค้นอะไร — แลกกับการเสียเวลาไม่กี่วินาทีต่อการค้น ซึ่งถูกกว่า context ที่เสียไปกับของไม่เกี่ยวมาก

เรื่องที่สาม: hooks — จุดเสียบ guardrail ที่เป็นโค้ด · กติกาบางอย่างสำคัญเกินกว่าจะ "ขอร้อง" ผ่าน prompt (โมเดลอาจลืม/ถูกหลอก — บทที่ 6 สอนแล้ว) · hooks ให้ผู้ใช้เขียนเช็กของตัวเองที่รันก่อน/หลังทุก tool call: ห้ามแตะไฟล์นี้ รัน formatter หลังแก้ทุกครั้ง เตือนเมื่อจะรันคำสั่งอันตราย — เบรกที่เป็นโค้ดบังคับได้ 100% ไม่ใช่คำขอร้องที่หวังว่าโมเดลจะฟัง · นี่คือหลัก "ชั้นแข็ง" ของบทที่ 6 ที่เปิดให้ผู้ใช้ต่อเอง

12.2 สิ่งที่ Claude Code สอนเรื่องการออกแบบ harness

  • default ปลอดภัย แต่ไม่รำคาญ — เริ่มด้วยการถาม แล้ว "จำ" คำอนุมัติ (allowlist) ให้ความถี่การถามลดลงเอง — ตรงตำราสมดุลของบทที่ 6
  • ยืดหยุ่นกับคุมได้ ไม่ใช่ขั้วตรงข้าม — Bash เปิดโลกเต็มที่ + permission/hooks คุมเข้ม = ได้ทั้งสอง
  • ทุกจุดขยายเปิดให้ผู้ใช้ — MCP (เพิ่ม tools), hooks (เพิ่ม guardrails), skills + CLAUDE.md (เพิ่มความรู้), subagents (ขยายสเกล) — ผู้ใช้ต่อเติมได้โดยไม่ต้องรอ vendor · harness ที่ดีจึงไม่ใช่ product ปิด แต่เป็น platform ที่คนอื่นสร้างต่อได้

12.3 เคสจริง: ตามรอย transcript ทีละขั้น

ทฤษฎีข้างบนอาจยังรู้สึกลอย ๆ — ตัวอย่างต่อไปนี้ประกอบจากรูปแบบงานที่เจอกันจริงบ่อย ๆ (คล้ายเคส config.py vs config_prod.py ของบทที่ 7) ไล่ทีละขั้นว่าแต่ละจังหวะคือกลไกจากบทไหนของเล่มนี้กำลังทำงานอยู่จริง พี่คนหนึ่งเจอบั๊กแล้วโยนงานให้ Claude Code ประโยคเดียว:

▸ transcript ช่วงที่ 1 — รับงาน แล้วค้นแบบ pull ไม่ใช่โหลดทั้ง repo

พี่: มีรายงานว่าลูกค้าสมัครสมาชิกซ้ำได้ ถ้าใช้อีเมลตัวพิมพ์ใหญ่เล็กต่างกัน
     (User@mail.com กับ user@mail.com ควรนับเป็นคนเดียวกัน) ช่วยแก้ให้หน่อย

กำลังหา logic การสมัครสมาชิกในโปรเจกต์…
▸ Grep(pattern: "def.*(signup|register)", glob: "**/*.py")
  ⎿ 2 matches — services/signup_service.py, admin/user_admin.py

▸ Read(services/signup_service.py)
  ⎿ 38 lines

จุดแรกที่เห็นชัด: agent ไม่ได้โหลดทั้ง repo มาอ่าน — มันเดาคำค้นจากคำในงาน (signup, register) แล้ว grep หาก่อน นี่คือ agentic search แบบ pull ที่บทที่ 3 สอนไว้ ผลลัพธ์ที่น่าสนใจ: grep เจอสองไฟล์ ไม่ใช่ไฟล์เดียว — เก็บไว้ในหัวก่อน จะกลับมาสำคัญอีกที

▸ transcript ช่วงที่ 2 — แก้ เพิ่มเทสต์ แล้วส่งให้ verifier ตัดสิน

เจอบรรทัดที่ตรวจซ้ำ: if User.query.filter_by(email=email).first(): raise DuplicateEmailError()
กำลังเช็ค test เดิมของไฟล์นี้ก่อนแก้…
▸ Grep(pattern: "def test_.*signup", path: "tests/test_signup.py")
  ⎿ 4 matches — ไม่มีเคสตัวพิมพ์ใหญ่เล็กเลย

▸ Edit(services/signup_service.py)
  ⎿ เพิ่ม email = email.strip().lower() ก่อนตรวจซ้ำและก่อนบันทึก

▸ Edit(tests/test_signup.py) — เพิ่มเคส test_signup_rejects_case_variant_email

▸ Bash(pytest tests/test_signup.py -q)
  ⎿ 5 passed

ถูกต้องตามตำรา: เช็กเทสต์เดิมก่อนแก้ (กัน regression) → แก้โค้ด → เพิ่มเทสต์ใหม่ให้ตรงกับบั๊กที่เพิ่งเจอ → รันเพื่อดูผล — closed loop เต็มรูปแบบของบทที่ 5 ผ่าน Bash เป็นประสาทสัมผัส ถ้าจบตรงนี้ก็ดูเรียบร้อยดี แต่ agent ที่ดีไม่หยุดแค่ไฟล์ที่แก้ — มันจำได้ว่าตอน grep เจอสองไฟล์ ไม่ใช่ไฟล์เดียว

▸ transcript ช่วงที่ 3 — เจอของนอกขอบเขต รายงานแทนที่จะลงมือเงียบ ๆ

เมื่อกี้ grep เจอ user_admin.py ด้วย — เช็คว่าใช้ logic เดียวกันไหม…
▸ Read(admin/user_admin.py)
  ⎿ พบฟังก์ชัน admin_create_user() — สร้าง user ตรง ๆ ไม่ผ่าน signup_service เลย
     ไม่มีการ .lower() เหมือนกัน — บั๊กเดียวกัน ช่องทางที่สอง

[แจ้งพี่] เจอช่องโหว่เดียวกันอีกจุดที่ admin_create_user() — นอกขอบเขตงานเดิม
  จะแก้ให้ด้วยเลย หรือแค่รายงานไว้ก่อน?

จุดนี้คือที่บทที่ 8 เตือนไว้เรื่อง scope: เจอ "ของที่ควรแก้เพิ่ม" ระหว่างทาง หลักที่ดีคือทำน้อยที่สุดที่แก้ปัญหา แล้วรายงานส่วนเกิน ไม่ใช่ลงมือเงียบ ๆ — ในเซสชันจริง กติกานี้มักตั้งไว้ล่วงหน้าใน CLAUDE.md ว่า "เจอบั๊กที่เกี่ยวข้องแต่นอกขอบเขต ให้ถามก่อน" (ความรู้ถาวรจากบทที่ 9 เสียบเข้ามาตรงจุดตัดสินใจนี้พอดี) สมมติพี่ตอบว่า "แก้ให้เลย" agent เดินหน้าต่อ:

▸ transcript ช่วงที่ 4 — แตะข้อมูลจริง เส้นบนแผนที่บทที่ 6 ถูกข้าม

▸ Edit(admin/user_admin.py) — เพิ่ม .lower() ให้ตรงกับ signup_service.py
▸ Edit(tests/test_user_admin.py) — เพิ่มเคสเดียวกันสำหรับ admin_create_user
▸ Bash(pytest tests/ -q)
  ⎿ 48 passed

ข้อมูลเก่าที่ซ้ำกันตั้งแต่ก่อนแก้ อาจยังนอนอยู่ในฐานข้อมูลจริง — ต้องเช็ค
ต้องรัน migration ทำความสะอาดอีเมลซ้ำของเก่า — คำสั่งนี้แก้ข้อมูลจริงใน production
  ⚠ ขออนุญาต: รัน scripts/normalize_existing_emails.py กับฐานข้อมูล production
     [อนุมัติครั้งนี้] [อนุมัติเสมอสำหรับคำสั่งนี้] [ปฏิเสธ]

blast radius × reversibility ของบทที่ 6 ทำงานตรงหน้าต่อหน้า: แก้โค้ดใน branch ย้อนได้ด้วย git จึงรันเทสต์ไปเรื่อย ๆ โดยไม่ถามทุกครั้ง (allowlist จำคำตอบไว้แล้ว) แต่พอคำสั่งขยับไปแตะข้อมูลจริงในฐานการผลิต — วงกว้างขึ้น ย้อนยากขึ้น — เส้นที่ FIG 6.1 วาดไว้ถูกข้าม ระบบจึงเลื่อนอำนาจตัดสินใจกลับมาที่คนทันที ไม่ใช่เพราะ agent "ไม่มั่นใจ" แต่เพราะกรงกำหนดไว้แบบนั้น

▸ transcript ช่วงที่ 5 — ประกาศเสร็จ ก็ต่อเมื่อ verifier ผ่านแล้วเท่านั้น

พี่: อนุมัติ

▸ Bash(python scripts/normalize_existing_emails.py --env=production)
  ⎿ พบอีเมลซ้ำ 3 คู่ — รวมบัญชีเรียบร้อย
▸ Bash(pytest tests/ -q)
  ⎿ 48 passed, 0 failed

สรุปให้พี่: แก้จุดตรวจซ้ำอีเมลใน signup_service.py และ user_admin.py ให้ไม่สนตัวพิมพ์เล็ก-ใหญ่
เพิ่มเทสต์คุมทั้งสองจุด และรวมบัญชีซ้ำเก่าที่มีอยู่แล้ว 3 คู่ในฐานข้อมูลจริง — เทสต์ทั้งหมดผ่าน 48/48

ประโยคสรุปมาหลังเทสต์ผ่านเท่านั้น — ตรงกฎของบทที่ 8: "ห้ามบอกว่าเสร็จ จนกว่า verifier จะผ่าน" ไล่ทั้งห้าช่วงย้อนกลับไป จะเห็นว่าไม่มีจังหวะไหนเป็น "ความฉลาด" ล้วน ๆ ของโมเดล — ทุกจังหวะคือจุดที่ harness ออกแบบไว้ล่วงหน้า: ให้มันค้นแทนยัด context (บท 3), ให้มันมีเครื่องมือที่คืนผลตรงพอจะตัดสินใจต่อ (บท 4), ให้มันตรวจตัวเองด้วยเทสต์ก่อนพูดว่าเสร็จ (บท 5, 8), ขีดเส้นว่าอะไรถามก่อนอะไรทำเลย (บท 6), มี CLAUDE.md กำกับพฤติกรรมตอนเจอทางแยก (บท 9) — ทุกกล่องใน FIG 12.1 ไม่ใช่แค่รายการฟีเจอร์ มันคือสิ่งที่กำลังทำงานจริงในทุกวินาทีของเซสชันข้างบน โมเดลตัวเดียวกันเป๊ะ ถ้าไม่มีรั้วพวกนี้ ผลลัพธ์จะไม่ใช่แบบนี้

สรุปบทที่ 12 — Claude Code ลงโครง 5 ชิ้นของบทที่ 1 ได้พอดี: CLAUDE.md/compaction/agentic search = context · Read/Edit/Bash + MCP = tools · เทสต์/lint ผ่าน Bash = feedback · permission/allowlist/hooks/sandbox = guardrails · narration/transcript/usage = observability · transcript 5 ช่วงข้างบนคือทุกกลไกนี้ทำงานร่วมกันจริงในงานเดียว · การตัดสินใจเด่น: tools น้อยแบบ general (Bash เปิดโลก + permission เข้มชดเชย), ค้นสดแทน index ล่วงหน้า, hooks = เบรกที่เป็นโค้ด · บทเรียนใหญ่: ชิ้นส่วนทั้งห้าออกแบบแยกกันไม่ได้ และ harness ที่ดีคือ platform

Read the full book