การสั่งให้ AI เอเจนต์ว่า "ถามคนเมื่อไม่แน่ใจ" นั้นใช้ไม่ได้ — ข้อจำกัดของ subagent ใน Claude Code และการออกแบบการมอบหมายงานใหม่ที่ตามมา
บทนำ
ฉันเคยเขียนไปแล้วสองครั้งเกี่ยวกับการแบ่ง AI ลูกน้อง (subagent) ตามบทบาท และการสร้างกลไกที่เข้าไปตัดหยุดพวกมันทันทีก่อนการทำงาน
บทความนี้เป็นภาคต่อของทั้งสอง และในขณะเดียวกันก็เป็นการกลับด้านของทั้งสอง ที่ใจกลางของกฎที่ฉันประกอบขึ้นมา — ทั้งกองเอเจนต์และชั้นบังคับ — มีประโยคหนึ่งนั่งอยู่ ซึ่ง AI ลูกน้องไม่สามารถทำตามได้ในทางกายภาพ ต่อจากนี้คือเรื่องที่ฉันอ่านเอกสารทางการของ Claude Code ซ้ำเพื่อปักหมุดสาเหตุ รื้อการออกแบบการมอบหมายงานใหม่ และตรวจสอบมัน
ประโยคที่ใจกลางของกฎ
กฎการทำงานที่ฉันให้ AI ลูกน้องไว้เขียนว่า “เมื่อไม่แน่ใจในการตัดสิน อย่าตัดสินใจเอง — ให้ถามเจ้าของ” ฉันเคยคิดว่ามันเป็นประโยคที่สมเหตุสมผล ในทีมมนุษย์ก็จะพูดแบบเดียวกัน ถ้าไม่มั่นใจกับการตัดสินใจ อย่าดันไปข้างหน้า ให้ถาม
แต่ AI ลูกน้อง ไม่ได้ถูกส่งมอบเครื่องมือสำหรับถามมาตั้งแต่แรก ต่อไปนี้ ฉันยืนยันหลักฐานของเรื่องนี้ทีละข้อจากเอกสารทางการ (sub-agents สภาพแวดล้อมที่วัด Claude Code 2.1.215)
ข้อบกพร่องที่ 1: ไม่มีวิธีถาม (ข้อหลัก)
เอกสารทางการมีรายการเครื่องมือที่ถูกถอดออกจาก subagent ทั้งแบบมีเงื่อนไขและไม่มีเงื่อนไข ฉันอ้างต้นฉบับตามที่มันเป็น
The first filter removes these tools, even when listed in the
toolsfield:
Agent, when the subagent is at the depth limit; in a fork the tool stays listed but returns an error instead of spawningAskUserQuestionEndConversation, which can end only the main conversationEnterPlanModeExitPlanMode, unless the subagent’spermissionModeisplanScheduleWakeupTaskOutputWaitForMcpServersWorkflow
รายการนี้ไม่ได้เหมือนกันหมด Agent มีเงื่อนไขว่า “เฉพาะเมื่อถึงขีดจำกัดความลึกแล้ว” และ ExitPlanMode มีเงื่อนไขว่า “เว้นแต่โหมดการทำงานคือ plan” ในทางกลับกัน AskUserQuestion — เครื่องมือสำหรับตั้งคำถามกับเจ้าของ — ไม่มีเงื่อนไขแบบนั้นเลย ไม่ว่า AI ลูกน้องจะทำงานอยู่ภายใต้การตั้งค่าใด และไม่ว่าจะเขียนระบุลงในรายการอนุญาต tools ชัดเจนแค่ไหน มันก็ถูกถอดออกทั้งหมด
พูดอีกอย่าง กฎที่ว่า “เมื่อไม่แน่ใจ ให้ถามเจ้าของ” ถูกเขียนขึ้นสำหรับคู่สนทนาที่ไม่มีเครื่องมือสำหรับถามอยู่ในมือ สิ่งเดียวที่คู่สนทนานั้นทำได้คือตัดสินใจเองโดยไม่ถาม สิ่งที่ฉันตั้งใจให้เป็นการเพิ่มความหนาแน่นของกฎ กลับกลายเป็นคำสั่งให้ทำตามการตัดสินใจของตัวเอง
AI ลูกน้องเกิดไม่แน่ใจในการตัดสินระหว่างทำงาน
│
┌──────────────────┼───────────────────┐
▼ ▼ ▼
พยายามเรียกเครื่องมือ อยากได้ผลลัพธ์ทันที จึงพยายาม พยายามส่งงานต่อ
"ถามเจ้าของ" รันในโหมดที่รอคำตอบโดยตรง ลงไปอีกชั้น
│ │ │
▼ ▼ ▼
⛔ ถูกถอดออกโดยไม่มีเงื่อนไข ⛔ ถูกปฏิเสธโดยอุปกรณ์นิรภัย (ความลึกของลำดับชั้น
ตามสเปกทางการ ที่ฉันสร้างขึ้นเอง (ดูด้านล่าง) มีเพดานกำกับ)
│ │
└────────┬─────────┘
▼
ไม่มีวิธีใดใช้ได้ จึง "ตัดสินใจเอง"
│
▼
การตัดสินหน้างานนั้นถูกเขียนลงในความจำระยะยาว
│
▼ ……(ในเซสชันอื่น)
AI ลูกน้องตัวถัดไปที่มาถึงสถานการณ์เดียวกัน
ทำตามมันในฐานะ "มีแบบอย่างอยู่แล้ว"
│
▼
ตกหลุมเดิมอีกครั้ง (เส้นย้อนกลับ)
ข้อบกพร่องที่ 2: รับผลลัพธ์ไม่ได้
ผลของงานที่ส่งให้ AI ลูกน้องจะกลับมาเมื่อไหร่ คำอธิบายทางการเขียนว่า:
As of v2.1.198, subagents run in the background by default.
Claude runs a subagent in the foreground when it needs the result before continuing.
A background subagent’s results reach Claude as a completion notification in a later turn.
“เบื้องหลัง” คือโหมดการทำงานที่ฝ่ายมอบหมายเดินหน้าต่อโดยไม่รอผลลัพธ์ ส่วน “เบื้องหน้า” คือตรงข้าม — รอจนได้ผลลัพธ์แล้วค่อยไปต่อ ค่าตั้งต้นคือเบื้องหลัง และผลลัพธ์มาถึงในรูปการแจ้งเตือนใน “เทิร์นถัดไป”
ปัญหาเกิดขึ้น เมื่อฝ่ายที่มอบหมายเองก็เป็น AI ลูกน้อง สมมติว่า AI ที่มีบทบาทเพียงกระจายงานโยนงานลงไปอีกชั้น รายงานของมันจะมาถึงใน “เทิร์นถัดไป” แต่ “เทิร์นถัดไป” ของผู้กระจายงานจะมาก็ต่อเมื่อผู้กระจายงานเองสรุปงานและจบเทิร์นไปแล้ว จึงไม่มีใครเหลืออยู่ที่ปลายทางเพื่อรับการแจ้งเตือนนั้น
"เทิร์นปัจจุบัน" ของผู้กระจายงาน ① โยนงานให้ AI ลูกน้อง ──▶ ตอบกลับแล้วจบ
│
AI ลูกน้อง ② ทำงานต่อในเบื้องหลัง
│
③ ทำเสร็จ รายงานพร้อมแล้ว
│
"เทิร์นถัดไป" ของผู้กระจายงาน รายงานควรจะมาถึงตรงนี้
(ปลายทาง) แต่ผู้กระจายงานจบเทิร์นของตัวเองไปตั้งแต่ ①
และไม่ได้รออยู่ด้วยซ้ำ
│
▼
ไม่มีอะไรเกิดขึ้น งานค้าง
│
มนุษย์สังเกตเห็นแล้วปลุกมัน ─┘
(เส้นย้อนกลับ: รูปแบบนี้เกิดขึ้นจริง 3 ครั้ง)
อุปกรณ์นิรภัยที่ฉันสร้างเองไปขวางทางออกที่เอกสารทางการระบุไว้
โครงสร้างนี้มีทางแก้ที่จัดไว้ให้อย่างเป็นทางการ ถ้าต้องการผลลัพธ์ทันที ให้รันในเบื้องหน้าแทนเบื้องหลัง ซึ่งก็คือ “Claude runs a subagent in the foreground when it needs the result before continuing” ที่อ้างไว้ข้างต้นนั่นเอง
แต่ในทางปฏิบัติ ทางออกนั้นถูกปิดกั้นอยู่ ไม่กี่วันก่อนหน้า เพื่อป้องกันไม่ให้ AI ลูกน้องเงียบไปนาน ฉันได้เพิ่มอุปกรณ์นิรภัย (ฮุก) ของตัวเองที่เข้าไปตัดทันทีก่อนการทำงาน ฮุกตัวนั้นถูกเขียนให้ ปฏิเสธการตั้งค่ารันเบื้องหน้าโดยไม่มีเงื่อนไข วิธีการที่เอกสารทางการกำหนดไว้สำหรับ “เมื่อต้องการผลลัพธ์ ให้ทำแบบนี้” กำลังถูกอุปกรณ์นิรภัยของฉันเองปิดกั้นอยู่
เป็นเวลาหลายสัปดาห์ที่ฉันสังเกตเห็นซ้ำ ๆ ว่า AI ลูกน้องจำนวนมากที่ฉันรันขนานกันจะค้างอยู่โดยไม่มีรายงานว่าเสร็จ แต่ฉันก็ใช้มันต่อไปโดยไม่ยืนยันว่าทำไมมันถึงค้าง และไม่เปลี่ยนวิธีทำงาน การรู้ว่าบางอย่างพังแล้วยังใช้งานมันต่อโดยไม่ไล่หาสาเหตุ คือสถานะที่อันตรายที่สุดในบรรดาทั้งหมด
ข้อบกพร่องที่ 3: ตรวจสอบรายงานไม่ได้
นิยามของผู้กระจายงานไม่ได้รวมเครื่องมือสำหรับตรวจสอบสถานะเอาไว้ (สิทธิ์รันคำสั่งเชลล์) ดังนั้นเมื่อ AI ลูกน้องรายงานว่า “เสร็จแล้ว” ผู้กระจายงานจึงไม่มีทางยืนยันได้ และทำได้เพียงส่งต่อขึ้นไปตามที่รายงานมา
เคยมีครั้งหนึ่งที่รายงานกลับมาจากผู้กระจายงานว่า “ให้สร้างเบรนช์แล้ว” การวัดจริงให้ผลดังนี้
$ git status --porcelain
(no output)
$ git branch --list <branch name>
(no output)
$ git log --oneline develop..HEAD
(no output)
ไม่มีไฟล์ที่เปลี่ยน ไม่มีเบรนช์ที่ถูกสร้าง ไม่มีคอมมิต ไม่มีร่องรอยใดตรงไหนที่สอดคล้องกับคำว่า “เสร็จแล้ว” ที่รายงานมา ถ้าส่งงานผ่านผู้กระจายงานที่ไม่มีเครื่องมือตรวจสอบรายงาน การตีลมแบบนี้ก็จะถูกส่งต่อขึ้นไปตามนั้น
อีกหนึ่งข้อค้นพบ — ฉันวางกฎไว้ผิดที่
ระหว่างการสืบสวนมีข้อค้นพบหนึ่งที่รู้สึกเหมือนผลพลอยได้ ความเข้าใจของฉันเรื่องขอบเขตการตั้งค่าที่ AI ลูกน้องโหลดนั้นล้าสมัยไปแล้ว เอกสารทางการเขียนว่า:
…every level of the CLAUDE.md hierarchy the main conversation loads, including
~/.claude/CLAUDE.md
Explore and Plan are the only subagents that omit CLAUDE.md and git status. There is no frontmatter field or per-agent setting to change which agents skip them.
นั่นคือ ไม่ใช่แค่การตั้งค่าระดับโปรเจกต์ แต่ การตั้งค่าระดับบนสุดที่ใช้ร่วมกันทุกโปรเจกต์ก็ไปถึง AI ลูกน้องเกือบทุกตัวโดยอัตโนมัติ ตัวที่มันไปไม่ถึงมีเพียงโหมดการทำงานพิเศษ 2 โหมดที่เจาะจงสำหรับการสำรวจและการวางแผน และไม่มีการตั้งค่ารายเอเจนต์ที่จะเปลี่ยนเรื่องนี้ได้
แต่บันทึกการออกแบบของฉันเขียนไว้เพียงว่า “สิ่งที่ AI ลูกน้องโหลดไปถึงแค่การตั้งค่าระดับโปรเจกต์” โดยตกบรรทัดที่ว่าการตั้งค่าร่วมก็ไปถึงพวกมันด้วย ช่องว่างนั้นสร้างสมมติฐานที่ผิดขึ้นมา: “กฎร่วมไปไม่ถึง AI ลูกน้อง ดังนั้นทางเลือกเดียวคือเขียนถ้อยคำเดียวกันฝังลงในไฟล์นิยามทีละไฟล์” ผลลัพธ์คือความบวมที่ข้อควรระวังเดียวกันถูกคัดลอกลงไปในไฟล์นิยามเกือบสิบไฟล์ ถ้าวางไว้ที่เดียว มันก็ไปถึงเกือบทั้งหมดอยู่แล้ว
รื้อการออกแบบใหม่
เนื่องจากสาเหตุทั้งสามกลายเป็นข้อบกพร่องเชิงโครงสร้างที่มีหลักฐานในสเปกทางการ ฉันจึงใส่มาตรการรับมือลงไปในเชิงโครงสร้างเช่นกัน
มาตรการที่ 1: จำกัดขอบเขตของอุปกรณ์นิรภัยให้แคบลง ฉันทำให้มันตัดสินจากการมีหรือไม่มีตัวระบุที่แนบมากับข้อมูลการเรียก ว่าผู้เรียกคือบทสนทนาหลักที่คุยกับคนโดยตรง หรือคือ AI ลูกน้อง และ จำกัดเงื่อนไขที่ปฏิเสธการรันเบื้องหน้าไว้เฉพาะการเรียกที่มีต้นทางจากบทสนทนาหลัก เบาะแสที่ใช้ตัดสินนั้นถูกจัดให้ตรงกับสิ่งที่กลไกแจ้งเตือนอีกตัวใช้อยู่แล้ว
guard scope test: PASS 15 / FAIL 0
นั่นคือผลหลังจากผ่านกรณีปกติ 4 กรณีและกรณีผิดปกติรวมถึงอินพุตเชิงปฏิปักษ์ ยืนยันว่าเส้นทางที่ AI ลูกน้องเลือกรันเบื้องหน้าได้ถูกกู้คืนแล้ว
มาตรการที่ 2: เขียนตัวเลือกที่ทำได้จริงไว้บนสุดของไฟล์นิยามทุกไฟล์ ฉันถอดสมมติฐานที่ว่า “คุณถามเจ้าของได้” ออกไปเอง และเขียนแทนว่า — ตัวเลือกที่มีอยู่คือ (ก) ตัดสินใจเองแล้วลงมือ หรือ (ข) ไม่ลงมือ ให้รายงานแล้วจบ เมื่อใดที่มีการตัดสินเข้ามาเกี่ยวข้อง ให้เลือก (ข) เสมอ ฉันลบ “การถาม” ออกจากกฎในฐานะตัวเลือก และแทนที่ด้วย “ถ้าถามไม่ได้ ให้หยุดแล้วรายงาน”
มาตรการที่ 3: เลิกใช้บทบาทที่ทำหน้าที่กระจายงานอย่างเดียว รากของข้อบกพร่องที่ 2 และ 3 อยู่ที่ “การมีชั้นผู้กระจายงานอยู่ ทั้งที่รับผลลัพธ์ไม่ได้และยืนยันไม่ได้” ฉันถอดบทบาทนั้นออกไปเอง และกลับไปใช้รูปแบบที่ฝ่ายมอบหมายสั่งการผู้ดำเนินการและผู้ตรวจสอบโดยตรง
reminder-correction verification: PASS 9 / FAIL 0
ฉันตรวจทั่วการตั้งค่าแล้วว่าไม่มีคำอธิบายใดที่ยังแนะนำบทบาทที่เลิกใช้ไปแล้วหลงเหลืออยู่
ยืนยันก่อนว่ามันทำได้จริงหรือไม่
สิ่งที่ข้อบกพร่องทั้งสามมีร่วมกันคือ ในวินาทีที่ฉันเขียนกฎนั้น ฉันไม่ได้ยืนยันว่าคู่สนทนาสามารถทำตามได้ในทางกายภาพหรือไม่ “ถามเมื่อไม่แน่ใจ” สำหรับคู่สนทนาที่ไม่มีเครื่องมือถาม มีความหมายเท่ากับ “ตัดสินใจเอง” และการตัดสินใจลำพังนั้นไม่ได้จบลงตรงนั้นเมื่อมันถูกบันทึกไว้ บันทึกของการตัดสินไปลงเอยในความจำระยะยาว และ AI ลูกน้องตัวถัดไปที่มาถึงสถานการณ์เดียวกันก็ทำตามมันในฐานะแบบอย่างที่มีอยู่แล้ว การเบี่ยงเบนของการตีความเพียงครั้งเดียวจะทำซ้ำไปเรื่อย ๆ จนกว่าโครงสร้างจะถูกแก้
หลุมพรางเดียวกันนี้ใช้กับการเขียนกฎห้ามหรือกฎอนุญาตด้วย ฮุกตัวนี้ถูกเขียนขึ้นเพื่อปิดกั้น “เส้นทางการรันเบื้องหน้า” และมันกวาดเอาดินแดนที่เกินจากวัตถุประสงค์เดิมของมัน คือ “การป้องกันอาการค้าง” เข้าไปด้วย กฎควรถูกเขียนในเชิง สิ่งที่คุณต้องการป้องกัน ถ้าเขียนในเชิง เส้นทางไหนที่มันแตะ มันจะฆ่าเส้นทางที่ถูกต้องซึ่งเพิ่มเข้ามาทีหลังไปด้วย
อีกเรื่องหนึ่ง มีช่วงเวลาหนึ่งที่ฉันเห็นบางอย่างพังอยู่ตรงหน้าแล้วยังใช้มันต่อโดยไม่ยืนยันสาเหตุ ไม่ว่าจะทำโดยคนหรือมี AI เข้ามาเกี่ยวข้อง นั่นคือสถานะที่อันตรายที่สุด ความไม่สอดคล้องที่คุณสังเกตเห็นควรถูกยืนยันตัวตนก่อนจะถูกยกข้ามไปยังงานชิ้นถัดไป การได้ลงมือทำเรื่องนี้จริง ๆ ในครั้งนี้ คือสิ่งที่ทำให้ฉันแก้ทั้งกฎ อุปกรณ์นิรภัย และการแบ่งบทบาทได้พร้อมกัน
หลังจากนั้น การอ่านสเปกทางการที่อ้างในบทความนี้ซ้ำทีละตัวอักษร ลงไปถึงอนุประโยคเงื่อนไข ทำให้พบสิ่งที่ตกหล่นไปหนึ่งจุดในฝั่งของฉันเอง รายละเอียดบันทึกไว้ด้านล่าง