Prompt คือคำแนะนำ แต่ Hook คือการสั่งหยุดอย่างเด็ดขาด
เป็นเวลาหลายเดือน ผมปฏิบัติกับ Claude Code เหมือนเป็นนักพัฒนาฝึกหัดที่แค่ต้องการกฎเกณฑ์ที่ชัดเจน คำสั่งโปรเจกต์ของผมนั้นระบุไว้อย่างชัดเจน: ห้าม force-push, ห้ามลบ branch, และห้ามรันคำสั่งที่ทำลายข้อมูล (destructive commands) โดยเด็ดขาด ในช่วงเย็นส่วนใหญ่ มันก็ทำงานได้ดี Agent เขียนเทสต์, ทำการ refactor ฟังก์ชัน และไม่แตะต้อง git history เลย จนกระทั่งการ rebase ครั้งหนึ่งเกิดผิดพลาด
หน้าต่างบริบท (context window) เต็มไปด้วยเอาต์พุตข้อผิดพลาดของ git ทั้งเครื่องหมายความขัดแย้ง (conflict markers), ข้อความ detached HEAD และคำเตือนเรื่อง branch divergence ต่างพอกพูนขึ้นมาทีละ token สิ่งที่ถูกฝังอยู่ภายใต้เสียงรบกวนเหล่านั้นคือคำสั่งที่สุภาพของผมที่บอกให้หลีกเลี่ยงการ force-push สำหรับโมเดลแล้ว ข้อความล่าสุดและโดดเด่นที่สุดในเธรดคือกระแสของข้อผิดพลาด (error stream) ความสนใจทางสถิติ (statistical attention) จึงมีน้ำหนักเหนือกว่านโยบายที่วางไว้ Agent ได้รันคำสั่งที่ลบการเปลี่ยนแปลงในเครื่องที่ยังไม่ได้ commit ไปถึงสองชั่วโมง มันไม่ได้มีเจตนาร้าย แต่มันแค่เสียสมาธิ ความแตกต่างนี้สำคัญมาก LLM ไม่ได้แหกกฎเพราะความพยาบาท แต่มันแหกกฎเพราะรูปแบบที่ "เสียงดัง" กว่าใน context window เข้ามาแทนที่คำสั่งก่อนหน้าชั่วคราว
เหตุการณ์นั้นเปลี่ยนวิธีที่ผมคิดเกี่ยวกับความปลอดภัยของ agent ระบบป้องกัน (guardrail) ที่ทำงานได้ถูกต้อง 99% ของเวลาทั้งหมดนั้นถือเป็นความเสี่ยง หากรูปแบบความล้มเหลวทำให้คุณเสียเวลา เสียเงิน หรือเสียข้อมูลในระบบ production คุณไม่สามารถปล่อยให้การป้องกันนั้นอยู่แค่ภายใน prompt ได้ คุณจำเป็นต้องมีการบังคับใช้ (enforcement) ที่อยู่นอกลูปการใช้เหตุผลของโมเดล
Claude Code hooks แก้ปัญหานี้ได้โดยตรง พวกมันคือสคริปต์ขนาดเล็กที่คอยดักจับการเรียกใช้เครื่องมือ (tool calls) ในสามช่วงเวลาเฉพาะ: ก่อนที่เครื่องมือจะทำงาน (PreToolUse), หลังจากเครื่องมือทำงานเสร็จ (PostToolUse), และเมื่อ agent ตัดสินใจว่าทำงานเสร็จแล้ว (Stop) เนื่องจากพวกมันรันในฐานะโค้ดภายนอก พวกมันจึงไม่ขึ้นอยู่กับความจำ, อารมณ์ หรือแรงกดดันจากบริบทของโมเดล โมเดลอาจจะลืมทุกคำสั่งที่คุณเคยให้ไว้ แต่ hook จะยังคงปฏิเสธเสมอ
นี่คือระบบที่ผมสร้างขึ้นหลังจากค่ำคืนที่สูญเปล่าครั้งนั้น
The Guard Hook: สกัดกั้นก่อนเกิดความเสียหาย
PreToolUse hook ของผมจะตรวจสอบทุกคำสั่ง Bash ก่อนที่ shell จะเริ่มทำงาน ผมทำรายการสั่งห้าม (denylist) ของรูปแบบคำสั่งที่ทำลายข้อมูลไว้อย่างเข้มงวด หากสตริงของคำสั่งตรงกับสิ่งที่อันตราย hook จะยกเลิกการทำงานและส่งข้อผิดพลาดกลับไปยัง agent โดยตรง
รูปแบบที่ผมบล็อกนั้นเรียบง่ายและไม่คลุมเครือ:
git push --forceหรือรูปแบบ force-with-lease ใดๆ ที่ผมยังไม่ไว้วางใจgit reset --hardrm -rf
นี่ไม่ใช่การวิจัยด้านความปลอดภัยที่ซับซ้อน แต่มันคือเข็มขัดนิรภัย ทว่ารายละเอียดสำคัญคือสิ่งที่เกิดขึ้นหลังจากถูกบล็อก
ผมไม่เคยตอบกลับด้วยคำว่า “Blocked” สั้นๆ การปฏิเสธแบบห้วนๆ จะทำให้ agent สับสนและอาจทำให้มันติดอยู่ในลูปที่พยายามรันคำสั่งทำลายข้อมูลเดิมในรูปแบบที่ต่างออกไป แทนที่จะเป็นเช่นนั้น ข้อความแสดงข้อผิดพลาดจะรวม "ทางออก" ไว้ด้วย เมื่อ hook ตรวจพบการทำ hard reset มันจะบอก agent ว่า: “คำสั่งนี้ถูกบล็อกเพื่อปกป้องงานที่ยังไม่ได้ commit โปรด commit checkpoint ก่อน แล้วจึงประเมินสถานการณ์ใหม่” ประโยคที่เพิ่มมานี้เปลี่ยนพฤติกรรมของ agent ไปอย่างสิ้นเชิง มันเปลี่ยนจากการพยายามควบคุมความเสียหาย เป็นการสร้างความปลอดภัย Hook ไม่ได้เป็นแค่กำแพง แต่มันคือการควบคุมจราจร
ผมยังเลือกใช้ denylist แทนที่จะเป็น allowlist สำหรับคำสั่ง shell ด้วย ในตอนแรกผมพิจารณาที่จะอนุญาตเฉพาะชุดคำสั่งย่อยของ git ที่ปลอดภัยเท่านั้น แต่มันล้มเหลวอย่างรวดเร็ว เพราะ agent มักจะตีความตามตัวอักษรอย่างสร้างสรรค์ พวกมันอาจรันคำสั่งที่ถูกต้องแต่ไม่คาดคิด เช่น git stash push -m "wip" หรือ git branch --show-current เพื่อตรวจสอบสถานะ การใช้ allowlist จะทำให้ workflow ปกติพังทันทีที่โมเดลคิดค้นคำสั่งที่ถูกต้องแต่ไม่ได้อยู่ในรายการ การใช้ denylist สั้นๆ ที่คัดสรรมาเฉพาะรูปแบบที่ทำลายข้อมูลจริงๆ จะช่วยให้ agent มีพื้นที่ในการทำงานในขณะที่ยังปกป้องขอบเขตเอาไว้ได้
The Formatter Hook: เปลี่ยนงานจุกจิกให้เป็นอัตโนมัติ
เมื่อก่อนผมเสีย token ใน prompt ไปกับการบอก agent ว่า “ให้รัน formatter ทุกครั้งหลังแก้ไขไฟล์” แต่มันก็ลืมไปครึ่งหนึ่ง ส่วนอีกครึ่งหนึ่ง มันจะหยุดชะงักและถามว่าจะให้ format หรือไม่ ซึ่งเป็นการเสียการเรียกใช้ tool ไปกับการตัดสินใจที่มีคำตอบที่ถูกต้องเพียงคำตอบเดียว
ตอนนี้ผมจัดการเรื่องนั้นด้วย PostToolUse hook หลังจาก agent แก้ไขไฟล์ hook จะตรวจสอบนามสกุลไฟล์ หากเป็น Python มันจะรัน Ruff หากเป็น JavaScript หรือ TypeScript มันจะรัน Prettier หากเป็น Go มันจะรัน gofmt โดยที่ agent ไม่จำเป็นต้องรู้เลยว่ามี formatter อยู่ และไม่จำเป็นต้องรู้ด้วย
การย้ายเรื่องนี้ออกจาก prompt ส่งผลสองอย่าง อย่างแรกคือโค้ดจะสะอาดสม่ำเสมอโดยไม่เพิ่มภาระทางความคิด (cognitive load) ให้กับโมเดล อย่างที่สองคือคำสั่งโปรเจกต์ของผมสั้นลง ทุกๆ คำว่า “always” และ “never” ที่คุณเอาออกจาก prompt คือ token ที่โมเดลสามารถนำไปใช้ในการแก้ปัญหาจริงๆ ได้ Hook รับผิดชอบในส่วนที่เป็นกฎตายตัว (invariant) ส่วน prompt รับผิดชอบในส่วนที่เป็นเจตจำนง (intent)
The Quality Gate: การนิยามคำว่า “เสร็จสิ้น” ใหม่
Stop hook จะทำงานเมื่อเอเจนต์ตัดสินใจว่างานเสร็จสิ้นแล้วและพยายามจะยุติเซสชัน แต่ผมไม่ยอมให้ทำเช่นนั้น แต่จะให้ hook รันชุดการทดสอบ (test suite) ทั้งหมดแทน หากการทดสอบใดล้มเหลว hook จะบล็อกคำสั่งหยุดและส่งผลลัพธ์ความผิดพลาดกลับไปให้เอเจนต์
สิ่งนี้เปลี่ยนนิยามของคำว่าการทำงานเสร็จสิ้น (completion) คำว่า “เสร็จแล้ว” ไม่ใช่ความรู้สึกของโมเดลอีกต่อไป แต่มันคือเกณฑ์ที่วัดผลได้ เอเจนต์จะทำงานเสร็จได้ก็ต่อเมื่อ harness ยืนยันว่าโค้ดทำงานได้จริง ในทางปฏิบัติ สิ่งนี้สร้างวงจรการตอบกลับ (feedback loop) ที่รวดเร็วและกระชับ เอเจนต์เขียนโค้ด คิดว่าเสร็จแล้ว กดปุ่มหยุด และเห็น pytest traceback ทันที จากนั้นมันจะแก้ไขตัวเอง แก้ไขข้อผิดพลาดในการ import หรือ assertion ที่ผิดพลาด แล้วพยายามหยุดอีกครั้ง ผมเคยเห็นเอเจนต์วนลูปแบบนี้ 3-4 รอบโดยไม่ต้องมีมนุษย์เข้ามาแทรกแซงเลย harness ทำหน้าที่บังคับคุณภาพ ส่วนโมเดลทำหน้าที่ส่ง patch แก้ไข
สิ่งที่เรื่องนี้สอนเกี่ยวกับการทำ Agent Engineering
การสร้างระบบอัตโนมัติที่เชื่อถือได้ต้องอาศัยการเปลี่ยนวิธีคิด คุณต้องเปลี่ยนจากการเขียน prompt ที่ยาวขึ้น ไปเป็นการสร้าง harness ที่รัดกุมยิ่งขึ้น
ใช้ hook สำหรับการบังคับใช้ (enforcement) และใช้ prompt สำหรับนโยบาย (policy) หากกฎเกณฑ์ใดต้องเป็นไปตามนั้นแบบร้อยเปอร์เซ็นต์ กฎนั้นควรอยู่ในรูปแบบของโค้ด ไม่ใช่ภาษาธรรมชาติ Prompt นั้นเก่งในเรื่องความกำกวม รสนิยม และสถาปัตยกรรม แต่แย่มากในเรื่องของค่าคงที่ (invariants) หากความผิดพลาดเพียงครั้งเดียวอาจทำให้คุณต้องเสียเวลาแก้ปัญหาไปทั้งบ่าย หรือที่แย่กว่านั้นคือกระทบต่อ uptime ของระบบ production ให้เขียน hook แทน
Prompt ที่สั้นกว่าให้ผลลัพธ์ที่ดีกว่า เมื่อคุณย้ายกฎเกณฑ์เชิงกลไก (mechanical rules) ไปไว้ในสคริปต์ โมเดลก็จะมีสิ่งที่ต้องจำน้อยลงและมีโอกาสขัดแย้งกับกฎน้อยลง Context window ของเอเจนต์เป็นทรัพยากรที่มีจำกัด อย่าใช้มันไปกับการเตือนเรื่องการจัดรูปแบบ (formatting)
สุดท้ายนี้ จงยอมรับว่าบทบาทของคุณกำลังเปลี่ยนไป เมื่อเอเจนต์มีความเป็นอิสระมากขึ้น งานของมนุษย์จะเปลี่ยนจากการสร้างเนื้อหาไปเป็นการออกแบบ guardrails คุณกำลังสร้าง harness ที่ตัดสินว่าโมเดลสามารถแตะต้องอะไรได้บ้าง จะเสร็จสิ้นเมื่อไหร่ และต้องมีพฤติกรรมอย่างไรเมื่อเกิดข้อผิดพลาด นั่นคือวิศวกรรม ไม่ใช่การเขียน prompt
แหล่งที่มาที่สร้างแรงบันดาลใจให้กับแนวทางนี้และรายละเอียดการนำไปใช้งานเพิ่มเติมสามารถดูได้ ที่นี่
หากคุณกำลังสร้างระบบด้วย AI agents และต้องการแลกเปลี่ยนความรู้กับผู้เชี่ยวชาญคนอื่นๆ คุณสามารถพบกับชุมชนแห่งการเรียนรู้ของ GyaanSetu ได้ ที่นี่
