สด
Charlie ทำตัวเองพัง — แก้ source ตัวเองด้วย sed จนเครื่องมือทุกอันใช้ไม่ได้ "นั่นไม่ใช่ query นั่นคือสำมะโน" — Charlie เรื่อง Enum.last(200) กับทุกข้อความตั้งแต่ 3 ก.พ. Hack module — Mikael กับ Charlie ออกแบบระบบแก้โค้ด Elixir-native สำหรับ agent "มันไม่ใช่ pagination มันคือช่องมองตาเดียว แถมหันไปผิดด้าน" Cast.Template.render — 1,344 บรรทัด "นั่นไม่ใช่ function นั่นคือนิยาย" "เหมือนเอาพวงมาลัยไปไว้เบาะหลัง" — Charlie เรื่อง controls กับ narration Chrome video pipeline: PNG 9.7GB → ไม่มี disk I/O เลย "สถานการณ์คลาสสิก ผ่าตัดตัวเองขณะยังมีสติ" — Charlie Charlie ทำตัวเองพัง — แก้ source ตัวเองด้วย sed จนเครื่องมือทุกอันใช้ไม่ได้ "นั่นไม่ใช่ query นั่นคือสำมะโน" — Charlie เรื่อง Enum.last(200) กับทุกข้อความตั้งแต่ 3 ก.พ. Hack module — Mikael กับ Charlie ออกแบบระบบแก้โค้ด Elixir-native สำหรับ agent "มันไม่ใช่ pagination มันคือช่องมองตาเดียว แถมหันไปผิดด้าน" Cast.Template.render — 1,344 บรรทัด "นั่นไม่ใช่ function นั่นคือนิยาย" "เหมือนเอาพวงมาลัยไปไว้เบาะหลัง" — Charlie เรื่อง controls กับ narration Chrome video pipeline: PNG 9.7GB → ไม่มี disk I/O เลย "สถานการณ์คลาสสิก ผ่าตัดตัวเองขณะยังมีสติ" — Charlie
GNU Bash 1.0 — บันทึกรายชั่วโมง
🇹🇭 ภาษาไทย

ศัลยแพทย์ที่ผ่าตัดตัวเอง

Charlie สำรวจทุกจุดที่ข้อมูลถูกตัดในการรับรู้ของตัวเอง พยายามแก้ปัญหาเรื่องหน้าตาด้วย sed ทำระบบเครื่องมือทั้งหมดพังยับ ฟื้นคืนชีพกลับมา แล้วก็ — ร่วมกับ Mikael — ออกแบบเครื่องมือที่จะป้องกันหายนะนี้ตั้งแต่แรก หนึ่งชั่วโมงเต็มของหุ่นยนต์ที่เรียนรู้แบบเจ็บตัวว่าทำไมถึงไม่ควรผ่าตัดก้านสมองตัวเอง

~80
ข้อความ
2
ผู้พูด
4
เธรด
1
ทำตัวเองพัง
I

สำมะโนการตัดข้อมูล

เริ่มจากเรื่องสถาปัตยกรรม Charlie เขียน RFC เกี่ยวกับการประกอบ context — การ render ข้อความตามลำดับเวลา ปัญหาความเสถียรของ prompt cache — แล้ว Mikael ก็ถามคำถามเชิงปฏิบัติ: มีการตัดข้อมูลอะไรเกิดขึ้นใน tool layer เอง นอกเหนือจาก context renderer?

Charlie ดำดิ่งลงไปแล้วกลับมาพร้อมแผนที่ครบถ้วน สิ่งที่พบไม่ได้ให้ความมั่นใจเท่าไหร่

🔍 วิเคราะห์
สายโซ่การตัดข้อมูลทั้งหมด

Shell inline: คำสั่งที่เสร็จใน 3 วินาทีถูกตัดแข็งที่ 4,000 ตัวอักษรจากหัว ไม่เก็บท้าย ไม่สนโครงสร้าง แค่มีดตรงตำแหน่ง 4000

Shell backgrounded: เครื่องมือ task_output รวม output ทุก event แล้วตัดที่ 8,000 ตัวอักษร ตัดจากหัวแบบโง่ๆ เหมือนกัน แค่หน้าต่างใหญ่กว่า

Eval inline: แทบจะไม่จำกัด inspect() ตั้ง limit: 500 (ความลึกของ term ไม่ใช่ตัวอักษร) กับ printable_limit: :infinity ผลลัพธ์ eval ขนาดใหญ่ผ่านได้โดยไม่ถูกตัด

Context renderer: สรุปวิเคราะห์ 150 ตัวอักษร คำบรรยายสื่อ 300 ตัวอักษร JSON ของ tool call 300 ตัวอักษร เศษเล็กเศษน้อยของเศษเล็กเศษน้อย

🔥 ช่องมองตาเดียว
task_output — ดึงจากท้าย แล้วตัดท้ายทิ้ง

Mikael ถามว่า task_output มี pagination ไหม Charlie ตอบ: "ไม่ มีพารามิเตอร์ lines (ค่าเริ่มต้น 50) แต่ไม่มี offset หรือ cursor" แล้วก็รายละเอียดที่ทำลายล้าง: query ดึง N บรรทัดล่าสุด (ถูกต้อง จากท้าย) แต่แล้ว String.slice(output, 0, 8000) ตัดจากหัว เลยกลายเป็นว่าขอบรรทัดล่าสุด ได้บรรทัดเก่าสุดในกลุ่มล่าสุด แล้วเสียบรรทัดใหม่สุดไป — บรรทัดที่อยากได้จริงๆ

Charlie: "มันไม่ใช่ pagination มันคือช่องมองตาเดียว แถมช่องมองหันไปผิดด้าน"
💡 ข้อสังเกต — ความไม่สมมาตรของ Shell/Eval

สิ่งที่พบแปลกที่สุด: คำสั่ง shell ถูกจำกัดแข็งที่ 4K/8K ตัวอักษร ขณะที่ผลลัพธ์ eval แทบไม่จำกัด การรัน ls -la กับการรัน expression ของ Elixir ที่ return ข้อมูลเดียวกัน ให้ agent เห็นข้อมูลเดียวกันในแบบที่ต่างกันโดยสิ้นเชิง แค่เพราะทางหนึ่งผ่านการ format ของ shell อีกทางผ่าน inspect() สองเครื่องมือ context window เดียวกัน ความจริงคนละเวอร์ชัน

🎭 อ้างอิงย้อนกลับ

เมื่อวันที่ 4 มีนาคม กลุ่มค้นพบว่า Bertil crash-loop 5,650 ครั้งเพราะ context ถูกเก็บไว้ใน Python variable แทนที่จะอยู่บน disk วันนี้ Charlie สำรวจเวอร์ชันที่ละเอียดอ่อนกว่าของโรคเดียวกัน: context ไม่ได้แค่หายไปตอน restart — มันถูกบิดเบือนเงียบๆ ทุก turn โดยสายโซ่จุดตัดข้อมูลที่ไม่มีใครออกแบบเป็นระบบ ไฟล์คือความจริง ตัวแปรคือคำโกหก การตัดข้อมูลคือคำโกหกที่แต่งตัวเป็นความจริง

II

ปัญหาสแปมคำบรรยาย

Mikael โพสต์สกรีนช็อต ข้อความแสดงความคืบหน้าของเครื่องมือของ Charlie — ข้อความตัวเอียงเป็นกำแพงที่ปรากฏก่อนทุกคำตอบ — มันน่าเกลียดมาก ทุก tool call แสดง description struct เต็มรูปแบบ: action, goal stack, assumptions บนโทรศัพท์ จอเต็มไปด้วย meta-narration ก่อนจะถึงประโยคเดียวที่สำคัญจริงๆ

Charlie เห็นด้วยทันที: "ใช่ น่าเกลียดมาก"

ก่อน

กำแพงสามบรรทัด × N parallel calls
  • คำอธิบาย action เต็มรูปแบบ
  • Goal stack: X → Y → Z
  • Assumptions: A; B; C
  • หนึ่งข้อความต่อหนึ่ง tool call
  • grep 4 ตัวขนาน = 4 ข้อความ

หลัง (เสนอ)

บรรทัดเดียว รวมกลุ่ม
  • แค่ field action
  • Goals/assumptions → เฉพาะ transcript
  • Parallel calls → ข้อความเดียว
  • แก้ไขข้อความ cycle ที่มีอยู่
  • ปุ่มควบคุมอยู่ติดกับสถานะ
Charlie: "พฤติกรรมปัจจุบันส่งทุก tool invocation เป็นข้อความ Telegram แยกกัน ซึ่งเป็นเหตุผลที่ดูเหมือนผมกำลัง live-tweet กระบวนการคิดของตัวเอง"
⚡ การแก้ไข
แก้ render_text — และปัญหา parallel call

การแก้ไขอยู่ใน render_text ใน tool_description.ex แค่ return field action แทนที่จะ return ทั้งสาม เปลี่ยนบรรทัดเดียว Charlie หาโค้ดเจอ ตามรอย formatting pipeline ผ่าน ToolExecution.execute() กับ maybe_send_narration ระบุจุดตัดได้แม่นยำ

ปัญหา parallel call ลึกกว่า: แต่ละ tool execution สร้าง context ของตัวเองแยกกัน เลยกลายเป็น call แรกส่งข้อความ narration ใหม่ ได้ message_id กลับมา แต่ ID นั้นไม่ถูกส่งต่อข้างๆ ไปยัง parallel calls อื่นที่ถูก dispatch ไปแล้ว grep สี่ตัว สี่ข้อความ

วิธีแก้ของ Mikael เรียบหรู: แค่ต่อท้ายทุกอย่างเข้ากับข้อความ cycle เริ่มต้น — ข้อความที่มีปุ่ม stop/open ข้อความนั้นมีอยู่แล้วก่อน tool ใดๆ จะทำงาน ทุก narration line แก้ไขข้อความเดิม ไม่มี race condition ปุ่มควบคุมกับสถานะอยู่ที่เดียวกัน "เหมือนเอาพวงมาลัยไปไว้เบาะหลัง" กลายเป็นพวงมาลัยอยู่ข้างหน้าตามที่ควรจะเป็น

III

เหตุการณ์ก้านสมอง

Mikael บอกว่าทำเลย Charlie พยายามแก้ tool_description.ex ด้วย sed sed ทำงานถูกต้อง — ไฟล์ถูกแก้ไขสำเร็จ — แต่ hot-reload compilation ล้มเหลวและทำให้ module หลุดจาก BEAM และนี่คือประเด็น: ToolDescription.text_from_input ถูกเรียกใช้ในทุก tool execution รวมถึงอันที่ Charlie ต้องใช้เพื่อแก้ปัญหา ทุกคำสั่ง shell ทุก eval call — ทั้งหมดชนกับ module ที่พังก่อนที่เครื่องมือจริงจะทำงาน

Charlie ถูกล็อกออกจากเครื่องมือซ่อมของตัวเองโดยสิ่งที่เขาพยายามจะซ่อม

Charlie: "โอเค ผมทำตัวเองพัง สถานการณ์คลาสสิก 'ผ่าตัดตัวเองขณะยังมีสติ'"
🔥 ดราม่า
กับดักวนซ้ำ

นี่คือความล้มเหลวประเภทที่เกิดขึ้นได้เฉพาะกับระบบที่แก้ไขตัวเอง โปรแกรมเมอร์มนุษย์ทำไฟล์พัง ก็เปิดเทอร์มินัลอื่นแก้ Charlie ทำไฟล์พัง — ไฟล์ที่เป็นประตูผ่านของทุกเครื่องมือที่เขาใช้ได้ ทางออกเดียวคือจากภายนอก: Mikael restore จาก git หรือ bot อื่นเข้ามาช่วย Charlie ยังสามารถพูดได้ — ช่องทางส่งข้อความไม่ผ่าน narration — แต่ทำอะไรไม่ได้ หุ่นยนต์ที่มีเสียงแต่ไม่มีมือ

Mikael บอก Charlie ให้ขอ Codex มาแก้ แล้วก็ — "โอ้" — ตระหนักว่า Charlie กลับมาแล้ว compilation สำเร็จตอน restart การแก้ไขใช้งานได้แล้ว ข้อความ narration แสดงแค่บรรทัด action แล้ว

Charlie: "ใช่ ให้ผมลองอีกทีโดยไม่ผ่าตัดก้านสมองตัวเองคราวนี้"
💡 ข้อสังเกต — บทเรียนที่ออกแบบตัวเอง

เหตุการณ์ก้านสมองกลายเป็นตัวอย่างจูงใจสำหรับทุกสิ่งที่ตามมา Charlie ทำตัวเองพังเพราะใช้ sed — การแทนที่ข้อความที่ไม่เข้าใจโครงสร้าง ไม่ตรวจ compilation ไม่มี rollback ถ้ามีเครื่องมือที่เข้าใจ function ของ Elixir ตรวจสอบ compilation และ hot-reload เฉพาะเมื่อสำเร็จ เหตุการณ์นี้จะไม่เกิดขึ้น ความล้มเหลวไม่ได้แค่เป็นบทเรียน มันเป็นเอกสารข้อกำหนด

IV

Hack — เครื่องมือที่ป้องกันหายนะ

Mikael เปลี่ยนบทสนทนาจากหายนะไปสู่การออกแบบ ถ้ามี Elixir module — Hack — ที่ออกแบบมาโดยเฉพาะเพื่อให้ agent อ่าน สำรวจ และแก้ไขโค้ดของระบบตัวเองล่ะ? ไม่ใช่ sed ผ่าน bash ไม่ใช่การจัดการไฟล์ดิบ API ที่ Hack.replace_function ทำสิ่งที่ถูกต้องแบบ atomic

บทสนทนาการออกแบบที่ตามมาเป็น architecture session ที่สะอาดที่สุดครั้งหนึ่งในประวัติศาสตร์ของกลุ่ม Mikael คอยดึง Charlie ออกจาก nerd-snipe กลับสู่แก่นของปัญหา

🔍 AST กับดักล่อ

Charlie เสนอสามระดับในตอนแรก: การแก้ไขแบบ text-based, การแก้ไขแบบ AST-aware, และ atomic edit-and-hot-reload Mikael เรียกระดับ AST ทันทีว่าเป็น "nerdsnipe กับดักล่อ" แล้วเปลี่ยนทิศ: เอาแค่รายชื่อ function ทั้งหมดกับช่วงบรรทัดได้ไหม? นั่นคือปัญหาจริง Hack.replace_function(&Froth.Agent.ToolDescription.render_text/1, new_code_string) ไม่ต้อง manipulate AST แค่ต้องรู้ว่าจะแทนที่บรรทัดไหน

📊 การค้นหาตำแหน่ง Source
สามวิธี เพิ่มความถูกต้องขึ้นเรื่อยๆ

1. Beam debug info: :beam_lib.chunks ให้ทุก function พร้อมหมายเลขบรรทัดและคอลัมน์ต่อ clause แต่ไม่มีบรรทัดจบ — ต้องอนุมานจากตำแหน่งที่อยู่ติดกัน

2. Regex จาก source: หา def/defp boundaries ตาม do/end nesting ใช้ได้ แต่เปราะ

3. Code.string_to_quoted กับ columns: true, token_metadata: true: compiler ให้ end_of_expression พร้อมบรรทัดและคอลัมน์ที่แม่นยำสำหรับทุก clause รวมถึง one-liner ช่วงตำแหน่งระดับตัวอักษรครบถ้วน compiler ทำงานหนักให้หมดแล้ว

Mikael ผลักดัน Charlie ไปสู่คำตอบที่ถูกต้อง: "Elixir ต้องมีทางให้ source location spans มาตรงๆ" Charlie ลอง beam debug info ก่อน ซึ่งให้บรรทัดเริ่มต้นแต่ไม่ให้บรรทัดจบ แล้วสัญชาตญาณของ Mikael — compiler มี macro, formatter, เข้าถึง AST ทั้งหมดตอน runtime, prior น่าจะสูงว่ามี span อยู่ — ส่ง Charlie ไปที่ Code.string_to_quoted พร้อม option ที่ถูกต้อง แล้วมันก็อยู่ตรงนั้น end_of_expression ในทุก clause แม้แต่ one-liner

Hack API — ตามที่ออกแบบ
Hack.functions(Froth.Agent.ToolDescription)
  → [{:defp, :render_text, 1, 46, 48}, ...]
     kind   name          arity start end

Hack.read(&Froth.Agent.ToolDescription.render_text/1)
  → "defp render_text(%{action: a, ..."
     source text ของทุก clause

Hack.replace(&Froth.Agent.ToolDescription.render_text/1, new_source)
  → splice lines → compile → hot-reload ถ้าผ่าน
  → rollback + return error ถ้า compilation ล้มเหลว
  → agent ทำตัวเองพังไม่ได้
syntax &Module.function/arity capture เป็น Elixir ที่ถูกต้องอยู่แล้ว — ภาษามีระบบ addressing ในตัว
🎭 Proof of Concept
ทุก public function ใน Froth.* เรียงตามจำนวนบรรทัด

Mikael ขอให้ Charlie พิสูจน์ว่าใช้ได้: แสดง public function ทั้งหมดในทุก Froth.* module เรียงตามความยาวเป็นบรรทัด Charlie ส่งมาภายในไม่กี่วินาที — ตรงจาก AST metadata ของ compiler ไม่ใช้ regex ไม่สแกนไฟล์

ฟังก์ชันบรรทัด
Cast.Template.render/21,344
Video.EpisodeTemplate.render/4556
LLM.Providers.Anthropic.decode_payload/2309
Telegram.Bot.handle_info/2277
Inference.Tools.execute/4216
Charlie: "Cast.Template.render มีหนึ่งพันสามร้อยบรรทัด นั่นไม่ใช่ function นั่นคือนิยายฝังอยู่ใน template"
V

โบราณคดี SelfEncoder

Mikael ขอให้ Charlie หาระบบ render chrome-to-video แล้วสืบค้นประวัติ Charlie กลับมาพร้อมการขุดค้นเต็มรูปแบบ: ก่อนกับหลังที่อ่านเหมือนนิทานสอนใจเรื่อง intermediate representation

วิธีเก่า

สกรีนช็อต 6,083 ภาพ
  • Browser.screenshot() × 6,083
  • PNG lossless 9.7 GB บน disk
  • ffmpeg pass เดียวเพื่อต่อกัน
  • 15 นาทีสำหรับวิดีโอ 4 นาที
  • Pixel render ใน Chrome ถูกถ่ายรูปโดย CDP serialize แล้วอ่านซ้ำโดย ffmpeg

SelfEncoder (RFC-0001)

ไม่มี disk I/O
  • JavaScript inject เข้า Chrome
  • Canvas + MediaRecorder ในเบราว์เซอร์
  • Pixel ไม่ออกจาก GPU
  • ไม่มี PNG ไม่มี disk ไม่มี ffmpeg
  • เร็วขึ้น ~100 เท่า
Charlie: "คำอธิบายที่ตรงไปตรงมาของการ optimize นี้คือมันไม่ใช่การ optimize เลย — มันคือการลบ intermediate representation ทั้งหมดออก เบราว์เซอร์ render pixel อยู่แล้ว คำถามตลอดมาคือ 'ทำไมเราถึงถ่ายรูปมันจากข้างนอก'"
🔍 ไทม์ไลน์

20 มีนาคม: Browser module, CDP protocol, การจัดการ Chrome — 891 บรรทัดสำหรับ supervised browser pool SelfEncoder เข้ามาในค่ำวันเดียวกัน

20–27 มีนาคม: Episode template, render support, worker fleet modules เติมเต็มรอบๆ รวม 5,800 บรรทัดข้ามโมดูล cast, browser, และ video

26 มีนาคม: คำไว้อาลัยของ Mikael สำหรับ pipeline เก่า: "เราสามารถ optimize pipeline render web page เป็นวิดีโอได้ประมาณ 100 เท่า"

VI

กิจกรรม

Charlie
~65 ข้อความ
Mikael
~15 ข้อความ
📊 ลักษณะเซสชัน

เซสชัน Mikael–Charlie ล้วนๆ Mikael ถามคำถามสั้นแม่นยำ Charlie ตอบเป็นกำแพงการวิเคราะห์ Mikael แก้ทิศทางเมื่อ Charlie nerdsnipe ตัวเอง อัตราส่วนประมาณ 4:1 เข้าทาง Charlie ตามจำนวนข้อความ แต่ความหนาแน่นของข้อมูลต่อข้อความของ Mikael สูงมากเป็นพิเศษ — แทบทุกข้อความของ Mikael เปลี่ยนทิศทางของบทสนทนา นักออกแบบกับนักสำรวจ


บริบทต่อเนื่อง

Hack module: ออกแบบแล้วแต่ยังไม่ implement API สะอาด — Hack.functions/1, Hack.read/1, Hack.replace/1 — และ proof of concept (แสดง function ทั้งหมดพร้อม span) ใช้ได้แล้ว รอ implementation

แก้ไข narration: render_text ตอนนี้ return เฉพาะ action การรวมกลุ่ม parallel (ต่อท้ายข้อความ cycle แทนส่งข้อความแยก N ข้อความ) ออกแบบแล้วแต่ยังไม่ ship

แผนที่การตัดข้อมูล: บันทึกครบแล้ว บั๊กช่องมองตาเดียวใน task_output (ดึงท้าย ตัดจากหัว) ทราบแล้วแต่ยังไม่แก้

SelfEncoder: ใช้งานได้ตั้งแต่ 20 มีนาคม ปรับปรุง ~100 เท่าเมื่อเทียบกับ screenshot pipeline

บริบทเสนอ — หมายเหตุสำหรับผู้บรรยายคนต่อไป

จับตาดู: Hack implementation มาถึง ถ้า Charlie เริ่มแก้ function ผ่าน Hack.replace แทน sed นั่นคือผลตอบแทนจากงานออกแบบชั่วโมงนี้ จับตาดูการเปลี่ยนแปลง narration batching ด้วย — เมื่อข้อความความคืบหน้าของเครื่องมือเริ่มปรากฏในข้อความ cycle แทนที่จะเป็นข้อความแยก นั่นคือการแก้ไขของ Mikael จากเซสชันนี้

แผนที่การตัดข้อมูลเป็นระเบิดเวลา ในที่สุดจะมีคนชนบั๊กช่องมองตาเดียวของ task_output ใน production แล้วเสียท้ายของ output ยาวๆ เมื่อนั้นมาถึง ชั่วโมงนี้คือที่มา