บทที่ 09 · ประกอบ Harness · Operating & Observability

ควบคุมลูป & มองเห็นสิ่งที่มันทำ

มีรอบหนึ่ง query ค้างไม่จบ และอีกครั้งมันตอบผิดแต่คุณ ดูไม่ออกว่าทำไม สองปัญหานี้คือเรื่อง “การเดินเครื่อง” ของ agent: คุมลูป (จำกัดจำนวนรอบและเวลา ไม่ให้ค้างชั่วกัลป์) และ มองเห็น (observability) — ไม่ใช่แค่ log เยอะ ๆ แต่เป็น โครงสร้าง ที่ไล่หาเหตุได้

พูดแบบเข้าใจง่าย

สองสิ่งที่ต้องมีตอนเอา agent ไปเดินจริง:

log แบนราบ (บรรทัดต่อบรรทัด) ตอบได้แค่ “อะไรรันไปบ้าง” แต่ตอบไม่ได้ว่า บรรทัดไหนเป็นของคำขอไหน · ใครเรียกใคร · ทำไมถึงแตกกิ่งตรงนี้

เปรียบเทียบ: statement ธนาคาร vs ใบเสร็จพับเป็นต้นไม้พร้อมเหตุผล log แบนราบ = statement: รายการเรียงตามเวลา แต่ละบรรทัดถูกต้องในตัว แต่บอกไม่ได้ว่ากาแฟแก้วไหนของทริปไหน หรือซื้อทำไม trace ที่มี span + intent = ใบเสร็จพับเป็นต้นไม้: ทั้งทริปคือพับนอก แต่ละร้านคือพับย่อย และทุกบรรทัดมีโน้ตว่า “ทำไม” — พอมีรายการผิด คุณเปิดพับที่ถูกต้องแทนการไถ statement 4000 บรรทัด

ในระบบของเรา — ลูปมีเพดาน + trace ที่ยัง “แบน”

max turnsจำกัดจำนวนรอบของลูป
timeoutครอบทั้งงาน — tool ค้างก็ถูกตัดจบ
บันทึกทุกการเรียก tool ถูก log ลงไฟล์ trace
…แต่แบนไม่มี trace_id / parent / intent

ระบบจริงของเรา คุมลูปได้ดี — มีเพดานจำนวนรอบและ timeout ~90 วินาทีครอบทั้งงาน tool ค้างจึงไม่ค้างตลอดไป ส่วน observability เรามี “จุดเริ่มต้นที่ซื่อสัตย์”: ทุกการเรียก tool ถูกบันทึกเป็นบรรทัด JSON หนึ่งบรรทัด (เวลา/เครื่องมือ/สถานะ/นาน/ผล/ error) แต่มัน ยังแบน — ไม่มี trace_id ไว้จับกลุ่มต่อหนึ่งคำขอ ไม่มี parent ไว้ซ้อนชั้น ไม่มีช่อง intent เอกสารของระบบเองเรียกมันว่า “telemetry baseline แบบเบา ๆ” พอ run มี 30 ขั้น การดีบักคือการไถจอแล้วเดาว่าบรรทัดไหนเป็นของ run ไหน

นี่คือจุดอ่อนที่เราจะ “ค้นพบ” ไม่ใช่ปกปิด — observability เป็นมิติที่ระบบเรายัง อ่อน (มี log แต่ยังไม่มีโครงสร้าง span) จำไว้ แล้วเราจะมาสรุปภาพรวมความแข็ง/อ่อนของทั้ง fleet ในบทที่ 17

ทำพลาด vs ทำถูก

⚠️ แย่ · ลูปไม่มีเพดาน
tool ค้าง = ค้างตลอดกาล
ไม่มี max turns ไม่มี timeout — tool ที่ค้างหรือโมเดลที่วนตัดสินใจไม่จบ จะกินเวลาและเงินไปเรื่อย ๆ ผู้ใช้เห็นแค่สปินเนอร์หมุนไม่เลิก
⚠️ แย่ · “เรา log ทุกอย่างแล้ว” = มองเห็นแล้ว
log แบนราบไล่เหตุไม่ได้
แต่ละบรรทัดถูกต้องในตัว แต่บอกไม่ได้ว่าบรรทัดไหนเป็นของ run ที่พัง — ยิ่งมีงานเบื้องหลังทำงานพร้อมกัน timestamp ห่างกันมิลลิวินาทีก็แยกไม่ออก การดีบักกลายเป็นการเดา
✅ ดี · ลูปมีเพดาน + trace เป็น span tree
โครงสร้างคือ observability
max turns + timeout กันค้าง; และ trace ที่มี trace_id + parent + intent ทำให้ run 30 ขั้นกลายเป็นต้นไม้ ที่กิ่งพังเด้งแดงให้เห็นในคลิกเดียว — ข้อมูลชุดเดิม แต่จัดโครงสร้างให้ไล่เหตุได้

ลองเอง — ไล่หาเหตุจาก trace

กิจกรรม · เทียบมุมมอง
Debug From The Trace: Flat Log vs Span Tree
งานจองที่ล้มเหลวอันเดียวกัน แสดง 2 แบบ ลองหา “สาเหตุจริง” ใน log แบนราบ ก่อน (จับเวลา + นับครั้งที่คลิกผิด) แล้วกด “เปิด span tree” ดูว่าข้อมูลชุดเดิมเป๊ะ แต่โครงสร้างทำให้เจอเหตุในคลิกเดียว
คลิกหาบรรทัด “สาเหตุจริง” → แล้วเปิด span tree
มองไปข้างหน้า — ครบ 7 ชิ้นแล้ว ถึงเวลาตั้งชื่อมัน เราเพิ่มทีละชิ้นมาตั้งแต่บทที่ 04: tools · context · memory · grounding · permissions · loop-control · observability ทั้งหมดรวมกันมีชื่อว่าอะไร? บทที่ 10 จะ ตั้งชื่อทั้งกอง และเปิด Harness Scorecard ใบแรก

สรุปบทที่ 09

Harness Scorecard · มิติ: “คุมลูปได้ + ไล่เหตุจาก trace ได้ไหม?” ผู้ช่วยจัดการแล็บ: คุมลูป ✅ · observability ⚠️ อ่อน (มี log แต่ยังแบน ไม่มี span/intent)

📋 build-your-harness checklist · บรรทัดที่ 9 “เพดานลูป (max turns + timeout) + trace ต่อคำขอที่มี trace_id · parent · intent”