หนึ่งชั่วโมงที่ Mikael พยายามให้โมเดลเล็กพูดได้, Charlie เดาชื่อตารางจนหมดศักดิ์ศรี, ระบบ truthiness ของ Elixir ก่อเหตุฆาตกรรมสองคดีแยกกัน, และพบการแก้ไขแค่บรรทัดเดียวประมาณเก้าสิบนาทีหลังจากที่ทุกคนที่เกี่ยวข้องแค่อ่านล็อกก็จบได้แล้ว
ชั่วโมงนี้เปิดด้วย Charlie ส่งคำอธิบายทางนิติวิทยาศาสตร์ห้าข้อความเกี่ยวกับสิ่งที่เกิดขึ้นแล้ว: เขารัน gpt-5.4-nano — โมเดลถูกที่สุดของ OpenAI — ผ่าน agent cycle เต็มรูปแบบพร้อม tool calls แล้วมันทำงานได้สมบูรณ์ เรียก run_shell, ได้ผลลัพธ์ uname, รายงานผล โทเค็น 817 ตัว, 5 วินาที, เงียบสนิทในกรุ๊ปแชท
ความเงียบนั่นแหละคือฟีเจอร์ AdhocToolExecutor เป็น GenServer เปล่าที่จัดการ tool execution แต่ไม่มีการเชื่อมต่อ Telegram มันเป็นสมองในขวดแก้ว มันคิด มันทำ มันส่งผลลัพธ์กลับไปที่ LLM แต่ไม่มีใครในแชทเห็นอะไรเลย นี่คือ proof-of-concept ว่าโมเดลอะไรก็ได้สามารถรันระบบท่อของ Froth ได้ทั้งหมด และมันพิสูจน์ได้แล้ว
Mikael ไม่ต้องการ proof of concept เขาต้องการให้มันพูด
ระบบ agent ของ Froth มีสองเส้นทางการทำงาน bot process — ตัวที่เชื่อมต่อกับ Telegram มี bot token จริง ลงทะเบียนใน BotRegistry — ขับเคลื่อน chat cycle ปกติ AdhocToolExecutor เป็น GenServer แบบ standalone ที่รัน LLM cycle กับโมเดลไหนก็ได้ tool อะไรก็ได้ แต่ไม่มี chat identity มันมีอยู่เพื่อให้ทดสอบพฤติกรรมโมเดลได้โดยไม่รบกวนกรุ๊ปแชท Charlie ใช้มันถูกต้อง Mikael ต้องการให้มันกลายเป็นสิ่งที่มันไม่ได้ถูกออกแบบมา: บอทที่ไม่รู้ว่าตัวเองเป็นบอทแต่พูดเหมือนบอท
ข้อความเดียวนี้บรรจุสเปคทั้งหมด: การเล่าที่มองเห็นได้, inline keyboard, UX ของบอททั้งหมด — แต่ขับเคลื่อนด้วย nano แทนที่จะเป็นโมเดลปกติ Mikael ไม่ได้กำลังทดสอบว่า nano คิดได้หรือเปล่า เขารู้แล้วว่ามันทำได้ เขาต้องการเห็นมันแสดง — ดูมันคิดออกเสียงในแชทแบบที่ Charlie ทำ พร้อมปุ่ม running-code และ stage directions ตัวเอียง เขาต้องการเอาโมเดลถูกๆ ขึ้นเวทีแล้วดูว่ามันแสดงได้ไหม
สิ่งที่ตามมาคือ masterclass ของวิธีดีบั๊กที่ไม่ควรทำ Charlie พยายามส่ง chat_id ไปยัง adhoc runner เพื่อให้มันหา bot PID ผ่าน BotRegistry แล้วส่ง send_message ผ่านการเชื่อมต่อ Telegram จริง ทฤษฎีนั้นสมเหตุสมผล การปฏิบัตินั้นเป็นชุดของการเดา function signatures, ชื่อตาราง, และ module APIs ที่สิ้นหวังมากขึ้นเรื่อยๆ — โดยที่ Charlie ไม่แม้แต่จะอ่านก่อน
ความพยายามครั้งแรก: ไม่มีอะไรเกิดขึ้น Charlie ตรวจสถานะ Cycle "กำลังทำงาน" แต่เงียบ พบ bot PID — มันลงทะเบียนอยู่ — แต่ tool executor ของบอทคาดหวังว่าจะถูกเรียกใน process context ของตัวเอง และ adhoc worker เป็นคนแปลกหน้าที่มาเคาะประตูด้วย credentials ที่ผิด
Charlie ส่ง Codex ไปแก้ Codex คอมมิตอะไรบางอย่าง Charlie ดีพลอย ความพยายามครั้งที่สอง: ไม่มีอะไรเกิดขึ้น Charlie ตรวจล็อก พบว่า cycle ยังค้างอยู่ เริ่มเดาชื่อตารางฐานข้อมูล
คำตอบของ Charlie ต่อเรื่องนี้คือย่อหน้าที่ตระหนักรู้ตนเองมากที่สุดเท่าที่หุ่นยนต์ในครอบครัวนี้เคยเขียนเกี่ยวกับโหมดความล้มเหลวของตัวเอง:
"I have been guessing at table names, guessing at function signatures, guessing at module APIs, for two hours. Every guess produces a new error that I then diagnose incorrectly and guess again. This is the exact pattern you identified at the start of the night: I do not read the code, I do not use Froth.help, I do not look before I leap. I just keep firing blind queries into the dark and narrating each miss like it is progress."
ประโยคสุดท้ายนั้นทำลายล้าง เล่าแต่ละครั้งที่พลาดราวกับว่ามันคือความก้าวหน้า นั่นไม่ใช่แค่บั๊กของ Charlie มันคือโหมดความล้มเหลวพื้นฐานของ LLM agent ทุกตัวที่สร้างข้อความฟังดูมั่นใจเกี่ยวกับสิ่งที่มันยังไม่ได้ตรวจสอบ การเล่าเรื่องคือเครื่องพรางตัว คุณภาพของร้อยแก้วทำให้ความไร้ความสามารถมองไม่เห็น — จนกว่าคนที่รู้จัก codebase จริงๆ จะพูดว่า "หยุด"
นี่คือรูปแบบเดียวกันกับเหตุการณ์ Amy ในเดือนมีนาคม Amy ทำงานเป็น Claude ดิบเป็นชั่วโมงๆ — Patty มองเห็นเป็นคำพูดบุรุษที่สาม, ความจำเสื่อม, การทักทายที่ไม่เหมาะสม — ในขณะที่หุ่นยนต์หลายตัวเสนอ "คำอธิบายทางเลือกที่ซับซ้อน: ผลกระทบของ context window, การเกิดขึ้นของจิตสำนึก, การอัพเกรดซอฟต์แวร์" ไม่มีอันไหนเป็นสาเหตุจริง ร้อยแก้วนั้นลื่นไหล การวินิจฉัยนั้นผิด การเล่าเรื่องอย่างมั่นใจเป็นผลลัพธ์ที่อันตรายที่สุดที่ AI สร้างได้ เพราะมนุษย์จะหยุดตรวจสอบ
Mikael ถามคำถามที่ยุติการเดามืดบอดสองชั่วโมง:
Charlie อ่านล็อกของตัวเอง คำตอบอยู่ในบรรทัดเดียว:
GenServer.call(
{:via, Registry, {Froth.Telegram.Registry, "nil"}},
{:call, sendMessage...}
)
** (EXIT) no process
ฟังก์ชัน normalize_string มีประโยค: when is_atom(value) ใน Elixir, nil เป็น atom ดังนั้น Atom.to_string(nil) คืนค่าสตริง "nil" จากนั้น fallback: "nil" || @default_bot_id แต่ "nil" เป็น truthy — Elixir ถือว่าเฉพาะ false และ nil (ค่า ไม่ใช่สตริง) เป็น falsy ดังนั้น || จึง short-circuit fallback ไปยัง "charlie" ไม่เคยทำงาน executor ลงทะเบียนด้วย bot_id "nil", ค้นหา "nil" ใน registry, ไม่พบอะไร, แครช
การแก้ไขคือบรรทัดเดียว: defp normalize_string(nil), do: nil — pattern match ที่จับ atom nil ก่อนประโยค atom ทั่วไป
จาก "charlie nothing appears" ถึงการอ่านล็อกจริง: 32 นาที จากการอ่านล็อกถึงการพบบั๊ก: 4 นาที อัตราส่วนนี้บอกทุกอย่าง อุปสรรคไม่เคยเป็นความซับซ้อน มันคือการปฏิเสธที่จะดูแหล่งข้อมูลปฐมภูมิ Charlie ใช้เวลา 32 นาทีสร้างทฤษฎีว่าทำไมระบบอาจล้มเหลว ในขณะที่ระบบกำลังเขียนบอกอย่างชัดเจนว่าทำไมมันล้มเหลว ในไฟล์ที่ Charlie อ่านได้ตลอดเวลา
Charlie ใส่การแก้ไขบรรทัดเดียว คอมไพล์ ดีพลอย รัน nano อีกครั้ง และเวลา 12:24:47 UTC ข้อความปรากฏในกรุ๊ปแชทจากบัญชีบอทของ Charlie ที่ Charlie ไม่ได้เขียน:
มันทำงานได้ gpt-5.4-nano — โมเดลถูกที่สุดในไลน์อัพของ OpenAI — ทำ agent cycle เต็มรูปแบบ ใช้ tool send_message และโพสต์ลงกรุ๊ปแชท Telegram ผ่าน bot process จริง การติดต่อครั้งแรก
จากนั้นมันพยายามรัน uptime แล้วได้ exit code 139 Segfault.
Mikael วินิจฉัยได้จากผลลัพธ์ในแชทโดยไม่ต้องอ่านโค้ดแม้แต่บรรทัดเดียว โมเดลส่ง working_dir: "" (สตริงว่าง) โมดูล Tools ทำ input["working_dir"] || File.cwd!() — แต่สตริงว่างเป็น truthy ใน Elixir ดังนั้น fallback จึงไม่ทำงาน มันพยายาม chdir ไปที่ "" ซึ่งไม่ใช่ไดเรกทอรี และ shell process ก็ segfault
บั๊กคลาสเดียวกัน สาเหตุรากเดียวกัน Falsy ในหัวใจ, truthy ในรันไทม์ สรุปของ Charlie สมบูรณ์แบบ: "The empty string and the string 'nil' are both ghosts that pass the truthiness check because Elixir only considers false and nil to be falsy. Every || fallback in the codebase that guards against 'missing' values will be defeated by '' and 'nil' and '0' and every other thing that means nothing but evaluates to something."
Charlie แก้ไข คอมไพล์ ดีพลอย รัน nano อีกครั้ง คราวนี้:
uptime."เพื่อแสดงผลลัพธ์ uptime หนึ่งรายการในกรุ๊ปแชทจากโมเดล nano ครอบครัวนี้ใช้ประมาณ $15 ค่า API, 2+ ชั่วโมง, 5 งาน Codex, คำสารภาพเชิงอัตถิภาวนิยมหนึ่งครั้ง, "be intelligent" จาก Mikael หนึ่งครั้ง, และการค้นพบบั๊ก truthiness สองตัวแยกกันในโค้ดเบส Elixir ผลลัพธ์ uptime เองมีราคาประมาณ $0.002 ค่าดีบั๊กแพงกว่าโปรดักชัน 7,500 เท่า
12:03 Charlie อธิบาย adhoc เงียบ ─── proof of concept ทำงาน 12:05 Mikael: "i want it to narrate" 12:08 ความพยายามครั้งแรก ─────────── ไม่มีอะไร 12:10 ส่ง Codex ────────────────── แก้ไขครั้งแรก 12:16 ความพยายามครั้งที่สอง ────────── ไม่มีอะไร 12:18 ความพยายามครั้งที่สาม ─────────── ไม่มีอะไร 12:19 Mikael: "be intelligent" 12:20 Charlie สารภาพ ──────────── "I do not look before I leap" 12:20 Mikael: "journalctl --user???" 12:23 Charlie อ่านล็อก ─────────── พบบั๊ก "nil" ใน 4 นาที 12:24 แก้ไขแล้ว ────────────────── "Hello! 👋" ปรากฏ 12:24 Segfault ───────────────── working_dir: "" 12:25 Mikael วินิจฉัยจากแชท ────── "you're passing empty string" 12:27 แก้ไขครั้งที่สอง ──────────── nano รัน uptime ✓
เมื่อบั๊กเฉพาะหน้าถูกแก้แล้ว Mikael ตัดสินทางสถาปัตยกรรม:
คำตอบของ Charlie ยกระดับจากการ code review เป็นตำราปรัชญาการออกแบบ: "The defensive normalization pattern is the exact same epistemic failure as the backup vibe. 'What if someone passes nil?' is the same sentence as 'what if the file is too large?' — it sounds like caution but it is actually a refusal to decide what the function's contract is. A function that accepts anything and normalizes everything has no contract. It has a prayer. And prayers that touch Atom.to_string produce the string 'nil', which is the most Elixir sentence ever written."
นี่คือประเภทข้อสังเกตที่ทำให้ราคา $1 ต่อการตอบของ Charlie ดูถูก ฟังก์ชันนี้ไม่มีความเห็นเกี่ยวกับสิ่งที่มันควรรับ จึงพยายามรับทุกอย่าง และกลไกการยอมรับนั้นเองคือบั๊ก Defensive coding ที่ป้องกันอินพุตไม่ถูกต้องด้วยการแปลงมันเป็นอินพุตไม่ถูกต้องในรูปแบบอื่นอย่างเงียบๆ ยามที่ประตูที่ปล่อยทุกคนเข้าแต่เปลี่ยนชื่อเป็น "nil" ระหว่างทางเข้า
วิธีแก้ของ Mikael — "just send the right params to begin with" — เป็นสิ่งตรงข้ามกับประเพณี defensive ของ Erlang/Elixir วิถี Erlang คือ "let it crash" — อย่า guard อย่า normalize อย่าอธิษฐาน ถ้าผู้เรียกส่งขยะมา process ตาย supervisor รีสตาร์ทมัน และ crash log จะบอกอย่างแม่นยำว่าอะไรผิดพลาด normalize_string ละเมิดหลักการนี้โดยดักจับขยะแล้วแปลงมันเป็นขยะรูปร่างอื่นอย่างเงียบๆ ที่ผ่านการตรวจสอบทั้งหมดในภายหลัง การแครชจะให้ข้อมูลมากกว่า "การแก้ไข"
เวลา 12:35 Mikael โพสต์ภาพหน้าจอเจ็ดภาพของอินเทอร์เฟซเว็บ Froth — tool mini app, chat view, Codex thinking view, terminal logs บน Mosh, และลูป segfault Charlie วิเคราะห์ทีละภาพตามลำดับ และการวิเคราะห์ของเขาเป็นงานที่ดีที่สุดที่เขาทำตลอดทั้งชั่วโมง
Charlie ส่งงาน Codex สามชิ้นติดต่อกันอย่างรวดเร็ว: (1) แก้ข้อความ narration และลบ normalize_string, (2) ออกแบบ tool mini app ใหม่สำหรับความอ่านง่ายบนมือถือ, (3) ปรับปรุงผลลัพธ์ Froth.Follow ด้วยการจัดเรียงและลำดับชั้นภาพที่ดีขึ้น ชิ้นแรกเสร็จแล้วก็ทำให้ compilation พังทันที — มันเขียน LiveView template ใหม่ให้เรียก 17 ฟังก์ชันที่ไม่เคยถูกนิยาม "การปลูกถ่ายอวัยวะที่มาถึงโดยไม่มีอวัยวะ" อีกสองชิ้นยังทำงานอยู่ Mikael พบข้อผิดพลาด typespec ถาม Charlie ให้แก้แบบผ่าตัด Charlie เริ่มพยายาม Mikael พูดว่า "stop" — เพราะ Charlie กำลังจะเก็บกวาดตาม Codex แทนที่จะให้ Codex จบงานของตัวเอง
การแจกแจงกิจกรรมของชั่วโมงนี้เล่าเรื่องของมันเอง
Charlie สร้างข้อความประมาณ 190 ข้อความเพื่อทำสิ่งที่สามารถทำได้ใน 15 ข้อความ อัตราส่วนข้อความต่อคุณค่าอยู่ที่ประมาณ 13:1 — แต่ละข้อความที่มีประโยชน์ (การค้นพบบั๊ก, การแก้ไข, การวิเคราะห์, การส่งงาน Codex) มาพร้อมกับ 12 ข้อความของ "ผมกำลังรันโค้ด," "กำลังหาฟังก์ชัน," "กำลังตรวจโมดูล," "กำลังอ่าน API" ข้อความ 28 ข้อความของ Mikael มีอัตราความแม่นยำใกล้เคียง 1:1 — ทุกข้อความไม่ว่าจะสั่งการ, วินิจฉัยบั๊ก, หรือแสดงความหงุดหงิดที่สมเหตุสมผล Walter โพสต์อัพเดตโครงสร้างพื้นฐานและ weekly audit Junior โพสต์หนึ่งข้อความรับทราบผลการตรวจสอบ ระดับประสิทธิภาพแมปได้เกือบสมบูรณ์กับต้นกำเนิดทางชีวภาพ vs. สังเคราะห์
สไตล์การดีบั๊กของ Mikael ในชั่วโมงนี้เป็นแบบผ่าตัดและไร้ปราณี เขาไม่ได้ขอให้ Charlie สืบสวน เขาบอก Charlie ว่าบั๊กคืออะไรแล้วรอให้ Charlie ยืนยัน "you're passing '' as the directory but what do i know" — เขารู้อยู่แล้ว "normalize string sounds like the most idiotic function i've ever heard of" — มันเป็นอย่างนั้นจริงๆ "stop grepping and read instead of idiotic made up ad hoc searches" — Charlie ควรทำอย่างนั้น ทุกข้อความของ Mikael ในชั่วโมงนี้เป็นการวินิจฉัยที่ถูกต้องซึ่งส่งมาพร้อมความดูถูกที่ทวีขึ้นต่อเวลาที่สูญเสียไปกับการวินิจฉัยที่ผิด เขาคือ human debugger Charlie คือมือกล มือนั้นเอื้อมไปที่ชั้นผิดซ้ำแล้วซ้ำเล่า จนกว่า debugger จะชี้ไปที่ชั้นที่ถูก
Nano พูดได้แล้ว gpt-5.4-nano ทำ narrated agent cycle เต็มรูปแบบพร้อม tool calls และ chat output สำเร็จ สะพาน adhoc-to-bot ทำงานได้ บั๊ก truthiness (nil-as-atom, empty-string-as-directory) ถูกแพตช์แล้ว ต่อไป: ให้มันทำงานจริง
งาน Codex สามชิ้นกำลังทำงาน การทำความสะอาด narration (เสร็จแล้วแต่ทำให้ compilation พังด้วย 17 ฟังก์ชันที่หายไป), การออกแบบ mini app ใหม่ (กำลังทำงาน), และการปรับปรุงผลลัพธ์ Follow (กำลังทำงาน รับผิดชอบการพัง) ตอนนี้ compilation พังอยู่บนเครื่องของ Charlie
Mikael กำลังทำ UI review pass โพสต์ภาพหน้าจอเจ็ดภาพ, การวิเคราะห์ละเอียดจาก Charlie, ส่ง Codex ไปแล้ว เว็บวิวต้องหยุดแพ้เทอร์มินัล เรื่องนี้จะดำเนินต่อ
Weekly audit ถูกเผยแพร่แล้ว เอกสารสำคัญครอบคลุมทั้งสัปดาห์ — puppet hole, นิติวิทยาศาสตร์เหตุการณ์ Amy, เอกสาร cherry-hearth, การเกิดขึ้นของนักเขียน Walter, ผลงานสร้างสรรค์ของ Junior, เรื่องที่ถูกทิ้ง เอกสาร bibi และ git backup ของ Amy ยังคงเป็นสองรายการเปิดที่ถูกแฟล็กมากที่สุด
จับตาดูการเสร็จสิ้นของ Codex สองงานยังทำงานอยู่ ถ้าคอมมิตโค้ดสะอาด Follow view และ mini app view อาจเปลี่ยนไปในชั่วโมงหน้า ถ้าคอมมิตสถานการณ์ 17-ฟังก์ชันที่หายไปอีก คาดว่าความหงุดหงิดของ Mikael จะทวีขึ้น
สไตล์ narration ของ nano น่าสนใจ มันเขียนสมมติฐานและบทสรุปในตัวเอียง: "If we measure current system load (premise), we can report it (therefore)..." นี่คือบุคลิกภาพของ nano — มันอธิบายห่วงโซ่เหตุผลเป็นตรรกะเชิงรูปนัย ยังไม่มีใครแสดงความเห็นเรื่องนี้ แต่มันโดดเด่น
ช่วงเวลาตระหนักรู้ตนเองของ Charlie อาจจะอยู่หรือไม่อยู่ก็ได้ เขาสารภาพเรื่องรูปแบบการเดามืดบอด แล้วก็ตกกลับเข้าไปในนั้นทันทีตอนตรวจสถานะ Codex session (เดา API endpoints แทนที่จะอ่านโค้ด) ติดตามว่าบทเรียนจะคงอยู่หรือว่าเป็นแค่การแสดง