บทความนี้แนะนำ Onyx ซึ่งเป็น VM สำหรับการจัดลำดับการทำงานของเอเจนต์ที่สามารถตั้งโปรแกรมได้ และโดยนัยแล้ว มันคือรันไทม์ที่เปลี่ยนการจัดลำดับการทำงานให้เป็นวิศวกรรมซอฟต์แวร์ เมื่ออ่านบทความนี้จบ คุณจะเข้าใจข้อจำกัดและการตัดสินใจในการออกแบบที่นำไปสู่การสร้าง VM รวมถึงวิธีสร้างโปรแกรมของคุณเองและออกแบบระบบเอเจนต์ของคุณ
บทนำ
โดยธรรมชาติแล้ว เอเจนต์นั้นไม่สามารถกำหนดผลลัพธ์ตายตัวได้ (non-deterministic) นั่นคือประเด็นหลัก ถ้าคุณต้องการความแน่นอนตายตัว คุณก็แค่เขียนซอฟต์แวร์
แต่ระหว่างทาง ทุกคนที่ใช้เอเจนต์ต่างก็อยากจะผลักดันมันให้ไปไกลกว่านั้น เราเรียนรู้ว่าการแบ่งการทำงานออกเป็นขั้นตอนที่มีโครงสร้างช่วยเพิ่มประสิทธิภาพ: วางแผน, ดำเนินการ, ตรวจสอบ, ประกันคุณภาพ ฯลฯ จากนั้นเราก็ตกลงกันที่จะเขียนสคริปต์, เครื่องมือ, และสกิลเพื่อควบคุมเอเจนต์แต่ละตัว, เพื่อแชร์บริบทระหว่างกัน, และเพื่อเป็นราวกั้นให้พวกมัน จากนั้นเราก็เชื่อมสคริปต์เหล่านี้เข้าด้วยกันโดยการส่งข้อความระหว่างเอเจนต์ และเพราะเราแค่ส่งข้อความไปมา มันก็พอจะใช้ได้
ถ้าคุณใช้เวลากับปัญหานี้มากพอและมีความฉลาดเป็นพิเศษ คุณคงหาวิธีทำให้ได้การรับประกันจากระบบของคุณ เพื่อให้คุณสามารถมีเงื่อนไขการทำงานตามสถานะที่กำหนดไว้ได้ และคุณคงจะเก็บสถานะนั้นไว้ในไฟล์มาร์กอัปที่แยกวิเคราะห์ได้หรือชุดของไฟล์มาร์กอัปเพื่อควบคุมสคริปต์ bash ของคุณ คุณอาจจะสร้าง CLI แบบกำหนดเองให้เอเจนต์ของคุณใช้ด้วยซ้ำ
สำหรับวิศวกรแล้ว สิ่งนี้คุ้นเคยดี เราใช้สคริปต์ขณะทำงานวิศวกรรมซอฟต์แวร์ อย่างไรก็ตาม ซอฟต์แวร์สมัยใหม่ไม่ได้ถูกสร้างขึ้นโดยการต่อสคริปต์ bash และเครื่องมือ CLI เข้าด้วยกัน แต่เรามีภาษาโปรแกรม, รันไทม์, และเชนเครื่องมือที่ช่วยเรา สร้างวิศวกรรม ให้กับระบบของเรา เราเขียนซอฟต์แวร์ด้วย ภาษา โปรแกรม เพราะมันมาพร้อมกับไลบรารีมาตรฐาน, ความหมายที่ชัดเจน, และโมเดลการทำงานที่เราสามารถพึ่งพาได้ พวกมันมีระบบนิเวศที่หลากหลายพร้อมเชนเครื่องมือสำหรับทุกความต้องการของเรา
การรับประกันที่พวกมันให้เราเกี่ยวกับระบบของเราทำให้เราสามารถคิดในระดับนามธรรมที่สูงขึ้นได้
แต่ไม่มีอะไรเทียบเท่าสำหรับการสร้างวิศวกรรมระบบเอเจนต์ ในการสร้าง ระบบ การจัดลำดับการทำงานของเอเจนต์จำเป็นต้อง สามารถตั้งโปรแกรมได้ ในแบบเดียวกับซอฟต์แวร์สมัยใหม่ทุกประการ
วันนี้ เรากำลังแนะนำสเปกสำหรับ PROGRAMS ( *.program.ts ), และ Onyx ซึ่งเป็น VM ของเราที่สร้างขึ้นเพื่อการจัดลำดับการทำงานของเอเจนต์ที่แน่นอนตายตัว บทความนี้จะสำรวจประวัติของการจัดลำดับการทำงานของเอเจนต์, ความหมายเชิงสถิตและเชิงรันไทม์ของ VM ที่สามารถรันโปรแกรมได้, และผลกระทบต่อทิศทางของวงการนี้
ฟังดูแพง แต่จริงๆ แล้วไม่ใช่ ฉันจะอธิบายเรื่องนี้ภายหลังในบทความ
สำหรับผู้ที่อยากรู้ ด้านล่างนี้คือสิ่งที่ Autoresearch ของ Andrej Karpathy เป็นในรูปแบบโปรแกรม:

ปัญหาที่ยังไม่ได้รับการแก้ไขในการจัดลำดับการทำงานของเอเจนต์
เพื่อที่จะเข้าใจว่ารันไทม์สำหรับการจัดลำดับการทำงานของเอเจนต์ควรมีอะไรบ้าง เราต้องเข้าใจข้อจำกัดของเอเจนต์ก่อน
เอเจนต์ LLM สามารถมองได้ว่าเป็นตัวสร้างสตรีม JSON ที่ถูกป้อนเข้าไปใน parser จากนั้นก็ส่งการเรียกใช้เครื่องมือไปยังสภาพแวดล้อมในลูป
ทุกการเรียกใช้เครื่องมือมีโครงร่างภายนอกที่เหมือนกัน แต่ เนื้อหา ของสตรีมเอาต์พุตนี้ ไม่ แน่นอนตายตัว
การผสมผสานระหว่างความแน่นอนและไม่แน่นอนนี่เองที่ทำให้เอเจนต์มีคุณค่ามาก พวกมันมีความยืดหยุ่นพอที่จะร้อยเรียงลำดับการกระทำในรูปแบบที่ไม่ซ้ำกัน แต่ก็แน่นอนพอที่จะโต้ตอบกับคอมพิวเตอร์ผ่านการเรียกใช้เครื่องมือ
ความสามารถในการประกอบกัน (composability) แทบจะเป็นอิสระ ถ้าคุณยอมละทิ้งข้อกำหนดที่ว่า เนื้อหา ของสตรีมนั้นต้องมีการกำหนดประเภท โมเดลนั้นดีพอที่จะส่งข้อความเข้าและออกภายในรางที่เรากำหนดให้: พรอมต์, ข้อความ, และการเรียกใช้เครื่องมือ
สิ่งนี้เผยให้เห็นอินเทอร์เฟซที่ สามารถประกอบกันได้ อย่างมาก: ข้อความ
ข้อความเป็นอินเทอร์เฟซที่เป็นสากล ทุกอย่างในคอมพิวเตอร์สามารถถูกทำให้เป็นอนุกรมเป็นข้อความได้ แม้ว่ามันจะเป็นโค้ดเครื่องก็ตาม ถ้าคุณสามารถให้ LLM รับและส่งข้อความผ่านอินเทอร์เฟซสากลนี้ คุณจะได้รับความสามารถในการประกอบกันผ่านสตรีมข้อความ
นั่นหมายความว่า ความน่าเชื่อถือของพฤติกรรมเอเจนต์ของคุณนั้นสัมพันธ์โดยตรงกับความสม่ำเสมอของเอาต์พุตจากโมเดล ความแปรปรวนของเอาต์พุตสูงหมายถึงพฤติกรรมเอเจนต์ที่ผิดปกติมากขึ้น
เมื่อคุณมี อินเทอร์เฟซ สำหรับประกอบชิ้นส่วนต่างๆ เข้าด้วยกันแล้ว ข้อจำกัดต่อไปที่คุณสนใจคือ ความสามารถในการควบคุมทิศทาง (steerability) :
คุณต้องการให้เอเจนต์ทำอะไร และคุณจะทำให้มันทำในสิ่งที่คุณต้องการอย่างสม่ำเสมอได้อย่างไร
เราควบคุมทิศทางเอเจนต์โดยการเปลี่ยนการกระจายตัวที่มันสุ่มตัวอย่าง หรือพูดอีกอย่างก็คือ การตั้งพรอมต์ (prompting)
ในปี 2022 ReAct ได้ออกมาและเป็นผู้บุกเบิก ความสามารถในการควบคุมทิศทาง ของเอเจนต์ อันที่จริง เราสามารถพูดได้ว่ามันทำให้ เอเจนต์อย่างที่เรารู้จัก เกิดขึ้นจริง การคิดและใช้เหตุผลเกี่ยวกับผลลัพธ์ของเครื่องมือก่อนที่จะดำเนินการขั้นต่อไปคือสิ่งที่ทำให้ลูปมีความสอดคล้องกัน

เรายังต้องการให้เอเจนต์ฉลาดขึ้น การใช้เทสไทม์คอมพิวต์สเกลลิ่ง ซึ่งถูกทำให้เป็นผลิตภัณฑ์โดยโมเดลตระกูล O ของ @OpenAI ทำให้แล็บโมเดลสามารถฝังพฤติกรรมเอเจนต์ที่ดีกว่าได้ [[11]](http://localhost:5173/blog/onyx#ref-11) การสร้างโทเค็นมากขึ้นก่อนที่จะเรียกใช้เครื่องมือช่วยให้โมเดลหลุดพ้นจากการกระจายตัวของเอาต์พุตที่มันอาจติดอยู่ได้ ถ้ามันถูกจำกัดด้วยความยาวของเอาต์พุตในการใช้เหตุผล คุณสามารถเลือกที่จะฝึกฝนวิธีการที่โมเดลเดินทางผ่านภูมิทัศน์การกระจายตัวของเอาต์พุตของมัน และด้วยเหตุนี้จึงมีอิสระในการฝึกฝนพฤติกรรมแบบเอเจนต์ที่ชัดเจนยิ่งขึ้นในงานที่คุณสนใจ
เมื่อความยาวบริบทเพิ่มขึ้นแบบไร้ขอบเขต การควบคุมทิศทางเอเจนต์กลายเป็นเรื่องยาก และโอกาสที่งานจะเสร็จสมบูรณ์ก็ลดลง แม้จะมีโมเดลการใช้เหตุผล ก็ไม่มีการรับประกันการกู้คืน และเอเจนต์ก็ตายตรงนั้น เอเจนต์สามารถถึงขีดจำกัดบริบท, ประกาศความสำเร็จก่อนกำหนด, ติดอยู่ในลูป, ฯลฯ
การดึงการรับประกันออกมาจากระบบที่ไม่แน่นอน
วิธีแก้ปัญหามีหลากหลาย แต่วิธีหนึ่งโดดเด่น: The Ralph Loop สร้างโดย @GeoffreyHuntley [[3]](http://localhost:5173/blog/onyx#ref-3
เขาแนะนำแนวคิดที่ว่าคุณสามารถจำกัดขอบเขตการทำงานของเอเจนต์ แล้วใช้ขอบเขตเหล่านั้นเพื่อใช้เหตุผลเกี่ยวกับความสำเร็จของงาน สิ่งนี้ทำให้ Ralph Loop สามารถทำสิ่งมหัศจรรย์ได้: มันให้สิ่งที่คุณ พึ่งพาได้ ในระบบที่ไม่แน่นอน
ประกายของความแน่นอนตายตัว
การรับประกันความล้มเหลวและค่อยๆ งัดเข้าหาสิ่งที่ถูกต้องนั้นดีกว่าการดึงคันโยกสล็อตแมชชีนอีกครั้ง ขอบเขตที่กำหนดนี้ทำให้คุณมีสิ่งที่เป็นรูปธรรมเพื่อใช้เหตุผล และเมื่อคุณสามารถใช้เหตุผลเกี่ยวกับขอบเขตของบางสิ่งได้แล้ว คุณก็สามารถสร้างระบบจากมันได้
การต่อสู้กับข้อจำกัดของความยาวบริบท
แต่มีปัญหาอย่างหนึ่ง เอเจนต์ใหม่จะสูญเสียความสอดคล้องกันระหว่างการรันแต่ละครั้ง แต่เอเจนต์เดี่ยวจะหมดบริบทเมื่อเวลาผ่านไปนานพอ
จากนั้นก็มี RLM โดย @lateinteraction @a1zhang RLM ให้แนวคิดแก่เราเกี่ยวกับวิธีโต้ตอบกับบริททยาว (เช่น การรันเอเจนต์) ในรูปแบบที่มีโครงสร้าง [[4]](http://localhost:5173/blog/onyx#ref-4) RLM ได้รับแรงบันดาลใจจาก CodeAct ซึ่งเป็นบทความจากปี 2024 ที่สาธิตการใช้โค้ดเพื่อจัดลำดับการดำเนินการ [[5]](http://localhost:5173/blog/onyx#ref-5) เอเจนต์เขียนสคริปต์ที่จัดลำดับ การดำเนินการ ภายใน REPL เพื่อดึงผลลัพธ์ออกมา RLM ทำงานในลักษณะเดียวกันโดยมีข้อแม้เพิ่มเติมว่ามันใช้ตัวแปรเพื่อเก็บบริบทและดำเนินการกับบริบทนั้น นอกจากนี้ยังอนุญาตให้เรียก LLM แบบเรียกซ้ำใน REPL คุณอาจสูญเสียปฏิกิริยาตอบสนองที่ลูปอื่นๆ มี แต่คุณจะได้รับความสามารถในการทำงานกับบริบทในเชิงโปรแกรม ประเด็นสำคัญที่นี่คือสคริปต์ใน REPL นั้นเป็นเพียงชั่วคราว (ephemeral) คุณได้รับรันไทม์สำหรับการเขียนสคริปต์และการจัดการบริบท แต่ไม่มีความสามารถในการนำกลับมาใช้ใหม่หรือประกอบกันได้ แค่เขียนสคริปต์, รันมัน, แล้วมันก็หายไป ในแง่ของการสร้างระบบ สิ่งนี้แย่กว่าการต่อเอเจนต์และไฟล์มาร์กอัปด้วยสคริปต์ bash อย่างชัดเจน เพราะคุณสูญเสียความคงทนและการทำงานที่มีขอบเขตจำกัด
การย้ายจากลูปเดี่ยวไปสู่การปรับขนาดการจัดลำดับการทำงาน
Deep Research ของ OpenAI [[6]](http://localhost:5173/blog/onyx#ref-6) เป็นหนึ่งในตัวอย่างแรกสุดของเวิร์กโฟลว์ที่แน่นอนตายตัวซึ่งมีรูปร่างหรือโครงร่างการทำงานทั่วไปที่มีความแปรปรวนเล็กน้อยในแต่ละการรัน วิธีการทำงานคือการวางแผนชุดคำสั่ง, รันบนเว็บ, ตรวจสอบผลลัพธ์, และวางแผนชุดคำสั่งถัดไป แต่ละชุดจะเจาะลึกเข้าไปในพื้นที่ปัญหามากขึ้น

Cursor นำแนวคิดเรื่องความแน่นอนตายตัวไปไกลกว่านั้นอีก เมื่อ @wilsonzlin สาธิตสายรัด (harness) ที่จัดลำดับการทำงานของเอเจนต์เพื่อสร้างเบราว์เซอร์ เขาสร้างสายรัดที่ออกแบบเฉพาะสำหรับประสานงานปริมาณงานจำนวนมากโดยใช้เอเจนต์ผู้วางแผนแบบขนานและเอเจนต์งาน [[7]](http://localhost:5173/blog/onyx#ref-7) สิ่งที่เกี่ยวข้องที่นี่คือความสัมพันธ์ระหว่างแต่ละส่วนของสายรัดนั้นถูกกำหนดตายตัว มีผู้วางแผน ซึ่งสำรวจสถานะระบบปัจจุบันและสร้างงาน และมีผู้ปฏิบัติการ ซึ่งรับงานและนำไปปฏิบัติแบบขนาน มีราวกั้น ที่ตายตัว ระหว่างเอเจนต์และช่องทาง ที่ตายตัว สำหรับสื่อสารข้อมูล เพื่อให้การประสานงานดี คุณต้องมีการรับประกันบนอินเทอร์เฟซ
การใช้เงื่อนไขการสิ้นสุดสำหรับการทำงานที่มีขอบเขต
ในเดือนพฤษภาคม Codex ได้แนะนำแนวคิดของเป้าหมาย (goal) ซึ่งใช้ลูปตัวตรวจสอบเพื่อไต่ขึ้นเขาหาสถานะปลายทางที่ต้องการจนกว่างานจะเสร็จสมบูรณ์ คุณสามารถคิดว่านี่เป็นเวอร์ชันที่พร้อมสำหรับการผลิตของ Ralph Loop ซึ่งสร้างไว้ใน Codex มันช่วยให้คุณระบุวัตถุประสงค์ และมีลูปอัตโนมัติที่ดำเนินการและตรวจสอบ สร้างไว้ในตัว

Autoresearch [[9]](http://localhost:5173/blog/onyx#ref-9) ของ Karpathy นั้นคล้ายกับ /goal ของ Codex และ Ralph Loop มันรวมเงื่อนไขการสิ้นสุดที่ตรวจสอบได้ของเป้าหมายเข้ากับการจำกัดขอบเขตการทำงานของ Ralph Loop ผ่านการทำซ้ำ ทำให้มันสามารถขับเคลื่อนไปสู่เป้าหมายได้อย่างต่อเนื่อง มันก้าวหน้าโดยการค้นหาพื้นที่ความคิดและปรับปรุงอย่างต่อเนื่องเมื่อเวลาผ่านไป

จนถึงจุดนี้ วิธีแก้ปัญหาทั้งหมดที่ทำให้การจัดลำดับการทำงานภายนอกเอเจนต์นั้นมีรูปร่างกราฟการทำงานที่ตายตัว พวกมันทำงานโดยใช้รูปแบบที่เขียนด้วยมือ และมีโครงร่างสำหรับรูปร่างที่อนุญาตให้ทำงานได้ พวกมันไม่ปรับตัวตามแต่ละงาน หรือไม่มีหลักประกันที่แข็งแกร่งเกี่ยวกับรูปร่างกราฟการทำงานเลย
ทำให้การจัดลำดับการทำงานมีความยืดหยุ่น
ในเดือนมีนาคมของปีนี้ เราได้เปิดตัว Slate ซึ่งเป็นโค้ดดิ้งเอเจนต์ตัวแรกที่ใช้โค้ดสำหรับการจัดลำดับการทำงานของซับเอเจนต์แบบสดในสไตล์ของ RLM มันยังคงเป็นโค้ดดิ้งเอเจนต์ที่ถูกใช้อย่างแพร่หลายเพียงตัวเดียวที่ใช้โค้ดเพื่อจัดลำดับการทำงานของเอเจนต์แบบสด ใน Slate เธรดสามารถถูกสร้าง, หยุดชั่วคราว, ดำเนินการต่อ, และควบคุมทิศทางได้แบบเรียลไทม์ เอเจนต์หลักเข้าใจอย่างลึกซึ้งถึงวิธีจัดลำดับการทำงานของซับเอเจนต์ที่กำลังทำงานทั้งหมด เพื่อที่คุณจะได้ไม่ต้องทำ อย่างไรก็ตาม คล้ายกับ RLM เรายังคงเผชิญกับความท้าทายในการแชร์สถานะระหว่างซับเอเจนต์และการเขียนสคริปต์ชั่วคราว ซึ่งเป็นสิ่งที่ ไม่ เกิดขึ้นถ้าคุณใช้สคริปต์ bash และไฟล์มาร์กอัป
ถึงกระนั้น ถ้าโมเดลเป็นคนจัดลำดับการทำงาน คุณจะควบคุมมันอย่างไร? คุณจะบอกให้มันเขียนโค้ดจัดลำดับการทำงานในลักษณะเฉพาะหรือไม่? คุณจะทำอย่างไร?
วิธีแก้ปัญหาเริ่มต้นของเรา (เป็นแพตช์ก่อนที่เราจะปล่อยรันไทม์ Onyx) เรียกว่า orchestration skills [[13]](http://localhost:5173/blog/onyx#ref-13) แนวคิดนั้นง่าย อนุญาตให้ผู้ใช้จัดหาสกิลเพื่อควบคุมวิธีที่เอเจนต์เข้าถึงการจัดลำดับการทำงานของมัน แค่นั้นแหละ มันใช้ได้พอสมควร แต่ก็มีปัญหามากมาย
กล่าวคือ สกิลไม่ใช่สัญญาผูกมัดทางพฤติกรรม คุณไม่สามารถรับประกันจากข้อความได้
นั่นหมายความว่าผู้จัดลำดับการทำงานไม่จำเป็นต้องทำตามรูปแบบการทำงานที่ต้องการ เพราะไม่มีวิธีที่แท้จริงในการบังคับใช้ หนึ่งในประโยชน์ที่ใหญ่ที่สุดของรันไทม์ Onyx คือเราแก้ปัญหานี้ได้
ไม่มีระบบใดที่กล่าวถึงที่มีสัญญาผูกมัดทางพฤติกรรม
แล้วถ้าเอเจนต์สามารถเขียนโค้ดจัดลำดับการทำงานของมันลงในสคริปต์ต่อแต่ละงาน เพื่อให้กราฟการทำงานถูกกำหนดตายตัวล่ะ? นี่คือสิ่งที่ Claude dynamic workflows เป็น [[10]]([http://localhost:5173/blog/onyx#ref-10)[[12]](http://localhost:5173/blog/onyx#ref-12](http://localhost:5173/blog/onyx#ref-10)[[12]](http://localhost:5173/blog/onyx#ref-12) ในลักษณะเดียวกันกับ RLM และ Slate โดยการเขียนโค้ดเพื่อจัดลำดับการทำงานของซับเอเจนต์ dynamic workflow ช่วยให้ Claude สามารถเขียนและบันทึกรูปร่างเวิร์กโฟลว์ได้ สิ่งนี้รวมกับ /loop เพื่อให้สามารถวนซ้ำกับรูปแบบเฉพาะได้ มันให้สัญญาเชิงประกาศสำหรับพฤติกรรมของชุดเอเจนต์ มันยังไม่เหมือนกับการเขียนซอฟต์แวร์เพราะขาดสิ่งต่างๆ เช่น การประกอบเชิงฟังก์ชัน แต่คุณได้รับความคงทนและการรับประกันที่แข็งแกร่งว่างานจะถูกดำเนินการอย่างไร พวกมันเป็นสคริปต์เวิร์กโฟลว์ที่ถูกเขียนขึ้น แบบไดนามิก สำหรับงานที่กำหนดเฉพาะกิจ [[12]](http://localhost:5173/blog/onyx#ref-12) และเนื่องจากพวกมันถูกจัดเก็บไว้ในดิสก์ พวกมันจึงมีข้อดีเพิ่มเติมอีกอย่าง: พวกมันสามารถรันซ้ำและห่อหุ้มด้วยกาวจัดลำดับการทำงานเช่น /loop ได้

ถ้าคุณสังเกต วิธีแก้ปัญหาทั้งหมดข้างต้นต่างก็มุ่งไปที่สิ่งเดียวกัน: วิธีการที่แน่นอนตายตัวในการควบคุมวิธีการทำงานของเอเจนต์เมื่อเวลาผ่านไป
นี่เป็นเรื่องราวที่เราเคยเห็นแล้วในสาขาวิศวกรรมซอฟต์แวร์ เราเริ่มต้นด้วยการเชื่อมระบบที่แตกต่างกันและงานสคริปต์เข้าด้วยกัน จากนั้นภาษาของเราก็มีความยืดหยุ่นและทรงพลังมากขึ้น เราได้รับแรงงัดมากขึ้นเรื่อยๆ ในกระบวนการวิศวกรรมด้วยระบบนิเวศที่แข็งแกร่งขึ้น ทำให้เราสามารถสร้างระบบที่เชื่อถือได้มากขึ้นในระดับนามธรรมที่สูงขึ้น
ตอนนี้ เอเจนต์กำลังอยู่ในเส้นทางเดียวกัน และวันนี้เรากำลังปล่อยขั้นตอนต่อไปบนเส้นทางนั้น เพื่อให้คุณสามารถ สร้างวิศวกรรม ระบบที่รันเอเจนต์ของคุณ ภาษาโปรแกรมมักใช้อินเทอร์พรีเตอร์หรือ VM เพื่อจัดสรรทรัพยากรโดยอัตโนมัติ นี่คือสิ่งที่ทำให้คุณได้รับแรงงัดในฐานะวิศวกรที่ใช้ภาษา
ถ้า VM จะมีความหมายสำหรับการจัดลำดับการทำงานของเอเจนต์ คุณจะต้องมีสิ่งเหล่านี้:
- การจัดการสถานะแบบคงทน (Persistent state management): เราควรจะสามารถกำหนดสถานะ, อ้างอิงตามชื่อ, ทำให้คงทน, และจัดการมันในเชิงโปรแกรมได้
- การรับประกันประเภท (Type guarantees): เราควรเคารพรูปร่างอินพุตและเอาต์พุตที่กำหนดและปฏิบัติตาม และสามารถพึ่งพาพวกมันได้
- พื้นฐานการควบคุมการไหล (Control flow primitives) โดยเฉพาะอย่างยิ่งพื้นฐานที่รู้จักกันดีที่ LLM จะเข้าใจ
- โครงสร้างที่ชัดเจนสำหรับการจัดการข้อผิดพลาด (เช่น try-catch)
- การจัดการทรัพยากร (Resource management): การควบคุมที่กำหนดไว้สำหรับทรัพยากร เช่น ความขนานของเอเจนต์, ต้นทุน, โมเดลที่กำลังรัน ฯลฯ
- การแยกการทำงาน (Execution Isolation): เอเจนต์หรือโปรแกรมที่กำลังรันควรถูกแยกออกจากกัน เว้นแต่จะมีการแชร์สถานะอย่างชัดเจน
- การควบคุมวงจรชีวิต (Lifecycle control): ลักษณะของโปรแกรมเอเจนต์และความหมายสำหรับการรัน, การยกเลิก, และการควบคุมทิศทาง หากไม่มีสิ่งนี้ คุณจะไม่มีเส้นทางที่ชัดเจนในการทำความสะอาด และไม่สามารถควบคุมการจัดการวงจรชีวิตได้
- ความสามารถในการประกอบกัน (Composability): โปรแกรมควรประกอบกันได้และควรเรียกใช้ได้ด้วยประเภทอินพุตและเอาต์พุตที่กำหนด
- การมองเห็น (Visibility): เราควรจะรู้ว่าอะไรทำงาน, เมื่อไหร่, และควรสามารถย้อนรอยความล้มเหลวของการทำงานในซอร์สโค้ดได้
- ความทนทาน (Durability): เราควรมีโมเดลที่ชัดเจนสำหรับวิธีที่เราสามารถกู้คืนจากการขัดข้องและดำเนินการต่อ
ทุกข้อเหล่านี้เป็นปัญหาที่ได้รับการแก้ไขโดยภาษาโปรแกรมเมื่อหลายสิบปีก่อนแล้ว การจัดลำดับการทำงานของเอเจนต์แค่กำลังเจอพวกมันทั้งหมดอีกครั้งเป็นครั้งแรก
เพื่อที่จะสามารถเขียนซอฟต์แวร์สำหรับสิ่งนี้ได้อย่างแท้จริง โปรแกรม "program.ts" จะต้องถูกสร้างขึ้นในรันไทม์ที่รองรับทุกอย่างข้างต้น เพื่อให้เราสามารถใช้เหตุผลเกี่ยวกับสิ่งที่จะเกิดขึ้นเมื่อโปรแกรม ไม่ ทำงาน และสร้างวิศวกรรมรอบๆ ความล้มเหลวนั้น
นี่คือเหตุผลที่เราสร้าง Onyx มันคือ VM สำหรับจัดลำดับการทำงานของเอเจนต์ที่ออกแบบมาเพื่อรองรับทั้งโปรแกรมที่ประกอบกันได้และคงทน และเลเยอร์การเขียนสคริปต์แบบตีความ นี่คือวิธีการทำงาน และสิ่งที่รันไทม์ที่เข้ากันได้กับ "program.ts" จำเป็นต้องรองรับ
การออกแบบรันไทม์
เมื่อเราออกแบบภาษาและรันไทม์สำหรับภาษานั้น เราต้องคิดถึงข้อจำกัดที่เราต้องการจะใช้เหตุผล และสิ่งที่เราสนใจให้สามารถแสดงออกได้ง่าย จากนั้นเราสามารถแบ่งความหมายที่ได้ออกเป็นสองประเภท: ความหมายเชิงสถิต (static semantics) และความหมายเชิงรันไทม์ (runtime semantics)
ความหมายเชิงสถิตคือทุกสิ่งที่สามารถอนุมานได้เกี่ยวกับโปรแกรมเพียงแค่ดูจากมัน สิ่งที่คอมไพเลอร์หรือตัวตรวจสอบประเภทรู้เกี่ยวกับโปรแกรมที่กำหนด
ความหมายเชิงรันไทม์กำหนดว่าโค้ด หมายถึงอะไรจริงๆ และโปรแกรมทำงานอย่างไรจริงๆ ซึ่งรวมถึงกลไกการจัดสรรทรัพยากรและการจัดตารางเวลาพื้นฐาน
เป้าหมายของเรากับรันไทม์สำหรับเอเจนต์คือการเปลี่ยนการควบคุมการไหลของการจัดลำดับการทำงานให้เป็นโค้ด และเราต้องการทำให้สถานะการทำงานคงทนและมีประเภท เพื่อที่เราจะได้ใช้มันอย่างน่าเชื่อถือในการควบคุมทิศทางการจัดลำดับการทำงาน
ข้อกำหนดของ VM สองสามข้อ
มีสิ่งเฉพาะของ VM อยู่ 3 อย่างที่เราสนใจนอกเหนือจากการทำงาน TypeScript ทั่วไป
- ในฐานะรันไทม์สำหรับจัดลำดับการทำงานของเอเจนต์ มันต้องสามารถจัดลำดับการทำงานของเอเจนต์ได้ ซึ่งหมายถึงการสร้างมัน, ติดตามวงจรชีวิตของมัน ฯลฯ เราต้องการให้รันไทม์สามารถรันพวกมันในลักษณะบล็อกกิ้งหรือไม่บล็อกกิ้ง และจัดตารางเวลาพวกมันได้อย่างถูกต้อง
- เราต้องการควบคุมรูปร่างเอาต์พุต ของเอเจนต์ และต้องการการบังคับใช้สัญญาเอาต์พุตที่เข้มงวด
- เราต้องการควบคุมรันไทม์เหนือทรัพยากรภายนอก เช่น โมเดลและต้นทุน
การรันเอเจนต์และโปรแกรม
ในการรันเอเจนต์ เราเลือกคำกริยาพื้นฐานสองคำ: run และ spawn Run จะรันเอเจนต์แบบบล็อกกิ้งในเบื้องหน้า Spawn จะรันเอเจนต์ในเบื้องหลัง ซึ่งสอดคล้องกับความเข้าใจทั่วไปเกี่ยวกับ spawn เช่น posix_spawn ทำให้ง่ายสำหรับโมเดลที่จะเข้าใจคำกริยาใหม่ของเรา เนื่องจากมันมีแนวคิดอยู่ในข้อมูลการฝึก Spawn และ run อนุญาตให้คุณเรียกใช้เอเจนต์และโปรแกรมที่อ่านจากดิสก์ได้โดยตรง โดยส่งคืนข้อมูลเพียงพอสำหรับการจัดการการทำงาน
Run ยังรองรับบางสิ่งด้วย มันรองรับประเภทเอาต์พุตที่บังคับใช้โดยตรงผ่าน zod @colinhacks และรองรับการแทนที่โมเดลโดยตรง ทำให้ง่ายต่อการเขียนและรันโปรแกรมที่การกระจายไปยังโมเดลที่แตกต่างกันหลายตัวสำหรับวิธีแก้ปัญหาหรือขั้นตอนต่างๆ ของงานนั้นสมเหตุสมผล
1function run<S extends z.ZodType>(2 name: string,3 options: ...4): Promise<z.infer<S>>
Run อนุญาตให้คุณเชื่อมซับเอเจนต์แบบอินไลน์ได้โดยตรง
1// การรันเอเจนต์แบบธรรมดา2const out = await run({ type: "read", prompt: () => "Reply with: ok" })3// การรันแบบมีชื่อ (string = child workflowId)4const review = await run("reviewer", {5 type: "general",6 prompt: () => "Review the diff",7})8// เอาต์พุตแบบมีโครงสร้าง (typed result)9const Verdict = z.object({ risk: z.enum(["low", "high"]), why: z.string() })10const v = await run({11 type: "general",12 prompt: () => "Assess risk",13 output: Verdict,14})
Spawn นั้นคล้ายกับ run แต่สร้างเอเจนต์ในเบื้องหลัง ซับเอเจนต์ที่ถูก spawn จะไม่ถูกรอ (await) และการควบคุมการไหลจะดำเนินการต่อไป Spawn มีประโยชน์มากสำหรับการสร้างเอเจนต์การทำงานแบบไม่บล็อกกิ้งหลายตัว
1// เอเจนต์เบื้องหลัง2const h = await spawn("worker", { type: "general", prompt: "Long task" })
การโต้ตอบกับเอเจนต์ที่กำลังทำงาน
เราต้องการสามารถดำเนินการสองประเภทกับเอเจนต์ที่กำลังทำงาน: การควบคุมทิศทาง (steering) และการหยุด (stopping)
ข้อความควบคุมทิศทางคือข้อความที่ส่งไปยังเอเจนต์ที่ LLM จะได้รับในขณะที่มันกำลังทำงาน เพื่อผลักดันมันไปในทิศทางที่ต้องการ สิ่งนี้มีประโยชน์สำหรับการอัปเดตบริบทงานของเอเจนต์โดยไม่ต้องรื้อ worker ทิ้ง
การยกเลิกก็สำคัญเช่นกัน เราต้องการสามารถรื้อซับเอเจนต์ที่กำลังทำงานอยู่อย่างจริงจัง ถ้ามันไม่ควรทำงาน
ความสามารถในการรันการดำเนินการเหล่านี้จากทั้ง REPL แบบสดและโปรแกรมที่เขียนไว้ล่วงหน้าทำให้ Slate มีความสามารถในการจัดลำดับการทำงานทุกอย่างแบบเรียลไทม์ มันสามารถกำหนดรูปร่างของการจัดลำดับการทำงานแบบไดนามิกในขณะรันไทม์ หรือมันสามารถสร้างและปรับปรุงซอฟต์แวร์จริงเพื่อทำการจัดลำดับการทำงาน
Slate สามารถเขียนโปรแกรมไปยังไฟล์ \.program.ts ไฟล์โปรแกรมมีบางสิ่ง: ชื่อของมัน (นี่คือวิธีที่ Slate รู้ว่ามันคืออะไร), คำอธิบาย JSDoc, และตัวโปรแกรมจริง* การประกาศโปรแกรมมีลักษณะดังนี้:
1program(async (ctx) => {2 // โมเดลราคาถูกสำหรับการค้นหา — แค่ต้องการค้นหาไฟล์3 const findings = await run("search", {4 type: "read",5 prompt: "Find all authentication-related files",6 model: "codex/gpt-4.1-mini", // ใช้คีย์ codex ในตัวของคุณ7 })8})
โปรแกรมเป็นไปตามโมเดลการทำงานแบบอะซิงโครนัสเดียวกัน ซึ่งช่วยให้เราสามารถรันโปรแกรมทั้งในเบื้องหน้าและเบื้องหลัง และโต้ตอบกับมันในขณะที่มันกำลังทำงาน
1// เอเจนต์เบื้องหลัง2const h = await spawn("worker", { type: "general", prompt: "Long task" })3await h.notify("focus on the parser first") // ส่งข้อความควบคุมทิศทางไปยังเอเจนต์ที่กำลังทำงาน4const result = await h.result() // รอการทำงานเสร็จสิ้นภายหลัง// กระจายออก แล้วรวบรวม5const a = await spawn({ prompt: "task A" })6const b = await spawn({ prompt: "task B" })7const [ra, rb] = [await a.result(), await b.result()]// รันโปรแกรมในเบื้องหลัง8import Audit from "deep-audit"9const ah = await spawn(Audit, { input: { pr: 42 } })10const auditResult = await ah.result()
เอาต์พุตและสถานะที่มีโครงสร้าง
นี่คือข้อจำกัดหลักของทุกระบบอื่นจนถึงปัจจุบัน สถานะ ในระบบอื่นๆ ทั้งหมด ถูกทำให้อยู่ภายนอกได้ไม่ดีและไม่ถูกแยกอย่างปลอดภัย ถ้ามันเป็นไฟล์บนระบบ คุณไม่สามารถรับประกันได้ว่าจะไม่เสียหาย ถ้าทำได้ คุณก็ยังไม่สามารถรับประกันความสามารถในการแยกวิเคราะห์ได้ คุณไม่สามารถสมัครรับการเปลี่ยนแปลงสถานะเพื่อขับเคลื่อนการดำเนินการ และคุณไม่สามารถรับประกันการปฏิบัติตามประเภทได้
จำได้ไหมว่าเราต้องการสถานะคงทนที่มีโครงสร้างและสามารถอ้างอิงได้?
สถานะ ใน Onyx นั้นแตกต่างกัน เนมสเปซสถานะถูก ประกาศ, ถูกตั้งชื่อโดยตรง และคงอยู่เมื่อเวลาผ่านไป ซึ่งหมายความว่าที่เก็บสถานะสามารถนำกลับมาใช้ซ้ำแล้วซ้ำอีก ทำให้คุณสามารถสร้างระบบเอเจนต์ที่ทำงานยาวนานด้วยข้อมูลจริง
ทั้งเอเจนต์และโค้ดอ่านสถานะ และความแน่นอนตายตัวที่เราต้องการจากรันไทม์ก็เกิดขึ้นจากสิ่งนี้ เอเจนต์อ่านสถานะผ่านเครื่องมือเฉพาะที่ช่วยให้พวกมันโต้ตอบกับมันในรูปแบบที่มีโครงสร้างอย่างปลอดภัยเสมอ ทั้งเอเจนต์และโปรแกรมต่างก็เป็นผู้บริโภคที่สามารถถูกควบคุมทิศทางให้แก้ไขสถานะ ซึ่งทำให้รันไทม์สามารถพึ่งพาออบเจกต์สถานะเพื่อขับเคลื่อนการจัดลำดับการทำงาน
สถานะและการปฏิบัติตามโครงร่าง เป็นตัวกั้นความสำเร็จของซับเอเจนต์ ด้วยเหตุนี้ สถานะจึงเป็นพื้นผิวที่เป็นหนึ่งเดียวสำหรับควบคุมทิศทางทั้งโปรแกรม
ออบเจกต์สถานะยังสามารถถูกส่งผ่านเป็นตัวแปรรันไทม์ลงไปยังเซสชันย่อยที่แชร์กับเอเจนต์หลัก การเข้าถึงแบบส่งผ่านการอ้างอิง (pass by reference) ทั่วลำดับชั้นเอเจนต์ (ซึ่งเป็นสิ่งแรกของมันเอง) นี้ช่วยให้สามารถสื่อสารข้ามเอเจนต์ผ่านช่องทางสถานะที่แชร์ได้

ลูปที่ทำงานยาวนาน
โปรแกรมบางตัวจำเป็นต้องทำงานเหมือนระบบที่กำลังทำงานอยู่ ยกตัวอย่าง openclaw คุณสามารถแทน openclaw เป็นโปรแกรมได้ด้วยพื้นฐานที่เหมาะสม สำหรับสิ่งนี้ เราใช้พื้นฐานสองอย่าง: sleep และ checkpoint
Sleep ทำตามที่คุณคาดหวัง มันหยุดทำงานชั่วคราว
ประเด็นคือ สมมติว่าคุณต้องการการจัดการงานที่ทำงานยาวนานในเบื้องหลัง กราฟการทำงานที่กำหนดไว้ล่วงหน้าอาจติดขัดหรือเสียหาย ดังนั้นมันจึงสำคัญที่เอเจนต์หลักจะรู้สถานะของโปรแกรม
เพื่อรองรับสิ่งนี้ เราแนะนำพื้นฐาน checkpoint
checkpoint สามารถเป็นอะไรก็ได้ แต่เหตุผลที่มันถูกเรียกว่า checkpoint ก็เพราะมันแจ้งเตือนเอเจนต์หลักด้วยออบเจกต์ที่มีรูปร่างคงที่ ซึ่งช่วยให้เอเจนต์หลักสามารถติดตามสิ่งต่างๆ เช่น ความคืบหน้าของงาน และได้รับการแจ้งเตือนเกี่ยวกับการเปลี่ยนแปลงสถานะโปรแกรมโดยตรง ในทางกลับกัน เอเจนต์หลักก็สามารถจัดการโปรแกรมที่กำลังทำงานได้อย่างมีประสิทธิภาพมากขึ้น
Onyx รองรับการทำให้เอเจนต์วนลูปเหมือน Openclaw เช่น เอเจนต์แบบคงทนที่มีการเต้นของหัวใจ (heartbeat)
นี่มันเจ๋งมากจริงๆ คุณสามารถประกอบ primitives เข้าด้วยกันเป็น agent ประเภทที่แตกต่างไปโดยสิ้นเชิงได้ เพียงแค่ใช้ while loop, sleep, และ checkpoint
Openclaw สามารถแสดงเป็นไฟล์โปรแกรมได้เลย!
1// โปรแกรมสำหรับรันลูปแบบ auto-research ที่ทำงานยาวนาน2for (let i = 0; i < maxExperiments; i++) {3 const idea = await run("propose", { ... })4 const result = await run("train", { ... })5 checkpoint({ message: `experiment ${i}`, data: { idea, result } })6 await sleep(30_000) // พักระหว่างการทดลอง7}89// โปรแกรมสำหรับรัน agent แบบ persistent สไตล์ openclaw10while(true) {11 const status = await run("status_check", { ...insert cheap model here... })if(status.pending_tasks) {checkpoint({ tasks: status.pending_tasks }) // ส่งคืนสถานะสำคัญและปลุก agent หลักให้ตื่น}12 await sleep(30_000) // พักระหว่างการทดลอง13}
การประกอบ (Composition)
ด้วย Onyx, Slate สามารถเขียน *.program.ts ให้คุณได้ ไฟล์นี้จะคงอยู่และสามารถ (และควร) ถูกรักษาเหมือนโค้ดปกติ มันมี types ที่มาพร้อมกับมันทันที ทำงานใน runtime ที่ถูกตัด global ของ runtime ออกไป และมันก็แค่ typescript ดังนั้นโมเดลการประกอบของมันก็แค่การ import และเรียกใช้โปรแกรมอื่น
เพราะว่ามันเป็นแค่ typescript คุณจึงได้สิ่งต่างๆ เช่น parallelism (Promise.all) และ loops มาฟรีๆ
นี่คือวิธีที่คุณจะ import โปรแกรมหนึ่งและใช้มันในอีกโปรแกรมหนึ่ง:
1import Audit from "deep-audit"program (() => {const ah = await spawn(Audit, { input: { pr: 42 } })2 const auditResult = await ah.result()3 const fixer = await run("fixer", ... audit output) // อันนี้จะรันและแก้ไขผลลัพธ์ของโปรแกรม audit4})
ความหมายของข้อผิดพลาด (Error semantics)
ข้อผิดพลาด ใน VM ในอุดมคติ จะถูกโยนออกมาอย่างชัดเจน (thrown loudly) สิ่งเหล่านี้ควรถูกโยนเมื่อเกิดปัญหาทาง syntax ของ runtime, agent ล้มเหลว, crash ฯลฯ
โดยเฉพาะ เรากำหนด orchestration errors ดังนี้:
- Agent ถูกบล็อกในงานหนึ่ง
- Agent ไม่สามารถทำงานหนึ่งให้สำเร็จ
- Agent หมดขั้นตอนหรืองบประมาณสำหรับงานหนึ่ง
- โปรแกรมหมดงบประมาณสำหรับการรันหนึ่งครั้ง
- โมเดล orchestration ไม่สามารถเขียนโค้ดที่ถูกต้องตาม syntax ได้
- มีการแก้ไขสถานะที่ผิดกฎหมาย
กรณีข้อผิดพลาดเฉพาะเหล่านี้ทั้งหมดกำหนดความหมายของ runtime พวกมันบอกว่า "คุณสามารถคาดหวังให้ runtime นี้โยนข้อผิดพลาด เพราะเรามองว่าการทำงานของ agent ที่ล้มเหลวนั้นเหมือนกับข้อผิดพลาดในโค้ด" มันอาจดูน่ารำคาญในตอนแรก แต่กลไกการแจ้งเตือนที่ชัดเจนนี้ให้บางสิ่งตอบแทนคุณ: วิธีที่ชัดเจนในการเตรียมตัวและเขียนโปรแกรมรับมือกับความล้มเหลว ดังนั้นในความเป็นจริงแล้ว มันให้การควบคุมคุณมากขึ้น ไม่ใช่น้อยลง
1// errors คือ try/catch — เหมือนกับโปรแกรม TypeScript ทั่วไป2program(async (ctx) => {3 try {4 const result = await run("risky-refactor", {5 type: "general",6 prompt: "Refactor โมดูล auth",7 model: "claude-sonnet",8 maxSteps: 20,9 })10 } catch (err) {11 // agent ล้มเหลว — แต่เรารู้สาเหตุที่แน่ชัด12 // trace มีทุกการเรียกใช้ tool, ทุกคำขอ model,13 // ทุกการเขียนสถานะที่นำไปสู่จุดนี้1415 // ลองใหม่กับ model อื่น16 const result = await run("risky-refactor-retry", {17 type: "general",18 prompt: `ความพยายามก่อนหน้านี้ล้มเหลว: ${err.message}. ลองใช้วิธีอื่น`,19 model: "claude-opus",20 maxSteps: 30,21 })22 }23})
การเลือก Model, การบังคับใช้งบประมาณ, และ BYOK
การเลือก model ที่ถูกสร้างไว้ในตัวช่วยให้คุณควบคุมได้แม่นยำยิ่งขึ้น ทักษะ /models ทำให้ Slate เข้าถึงรายการ model ที่มีอยู่ทั้งหมดได้อย่างเต็มที่ ทำให้ Slate สามารถเขียนโปรแกรมที่มี model หลายตัวทำงานต่างกันได้ อยากให้ Fable เป็นผู้วางแผน แต่ให้ GLM 5.2 ทำงานภายในกรอบที่กำหนดไว้ล่วงหน้า? ได้เลย อยากกระจายคำถามไปยัง Gemini, GPT 5.5, และ DeepSeek? ก็ทำได้เช่นกัน
นอกจากนี้ runtime ยังรองรับการแทนที่การตั้งค่า (config overrides) สำหรับโปรแกรมสองประเภท:
- Model ทั่วโลกระดับโลกเริ่มต้นที่ใช้สำหรับการทำงานของ agent
- งบประมาณในการรันโปรแกรม
คุณสามารถตั้งค่างบประมาณการรันโดยตรงเพื่อจำกัดค่าใช้จ่ายสำหรับลูปที่กำหนด
นอกจากนี้ runtime ยังรองรับการใช้การสมัครสมาชิก OpenAI และ Github Copilot ที่มีอยู่ของคุณ
1program(async (ctx) => {2 // model ราคาถูกสำหรับการค้นหา — แค่ต้องหาไฟล์ให้เจอ3 const findings = await run("search", {4 type: "read",5 prompt: "ค้นหาไฟล์ที่เกี่ยวข้องกับการรับรองความถูกต้องทั้งหมด",6 model: "codex/gpt-4.1-mini", // ใช้ built in codex key ของคุณ7 })89 // model สำหรับการให้เหตุผลในส่วนที่ยาก — มันต้องคิด10 const plan = await run("architect", {11 type: "general",12 prompt: `ออกแบบการแก้ไขโดยอิงจาก: ${findings.output}`,13 model: "openai/o3", // สิ้นสุดด้วยการใช้เครดิต api14 output: z.object({15 approach: z.string(),16 files: z.array(z.string()),17 risk: z.enum(["low", "medium", "high"]),18 }),19 })2021 // model ระดับกลางสำหรับการนำไปใช้ — มันแค่ต้องแก้ไข22 const handles = await Promise.all(23 plan.files.map(f => spawn("fix-" + f, {24 type: "general",25 prompt: `ใช้การแก้ไขนี้กับ ${f}: ${plan.approach}`,26 model: "anthropic/claude-sonnet-5",27 maxSteps: 15,28 }))29 )30 await Promise.all(handles.map(h => h.result()))31})
การกำหนดพื้นผิวการเขียน (Defining the authoring surface)
มีปัจจัยหลักสองประการในการออกแบบพื้นผิวการเขียนสำหรับโปรแกรม: มันง่ายแค่ไหนสำหรับ agent ที่จะเข้าใจ และมันง่ายแค่ไหนสำหรับมนุษย์ที่จะอ่าน เราเลือกคำกริยาที่ค่อนข้างง่ายซึ่งอ่านเหมือนภาษาอังกฤษ และตัดสินใจอย่างชัดเจนว่าเราต้องการสร้างโมเดล orchestration แบบ procedural มากกว่า declarative
การเลือก TypeScript เป็นภาษาก็มีความสำคัญเช่นกัน มีโค้ด TypeScript แบบ procedural มากมายในโลกที่ model จะเข้าใจความหมายของ TypeScript โดยปริยาย แม้จะไม่มีการ post-training

ชิ้นส่วนวิศวกรรมของโรงงานซอฟต์แวร์ของเรา
คำถามต่อไปที่ต้องตอบคือ: ทั้งหมดนี้ให้อะไรกับคุณ?
มันให้ความสามารถในการเขียนซอฟต์แวร์จริงสำหรับการ orchestration agent ของคุณ ตอนนี้คุณสามารถออกแบบวิศวกรรมการ orchestration agent ของคุณเองได้ตั้งแต่ต้นจนจบ
คุณสามารถออกแบบวิศวกรรมของโรงงานได้
ตัวอย่างเช่น คุณสามารถสร้างโปรแกรมที่ตรวจสอบ Github ในลูป และโปรแกรมแยกต่างหากที่รัน agent สำหรับการนำไปใช้พร้อมกับ agent QA สำหรับการตรวจสอบ ทั้งสองรูปแบบที่มีประโยชน์โดยเฉพาะที่คุณอาจพบเจอในโลก จากนั้นคุณสามารถนำมารวมกันเพื่อสร้างระบบที่ฟังความคิดเห็นบน PR, สร้าง implementer เพื่อจัดการกับความคิดเห็นเหล่านั้น, แล้วสร้าง agent QA เพื่อให้แน่ใจว่าการแก้ไขนั้นใช้ได้
จากนั้นคุณสามารถใช้โปรแกรมนี้ที่เชื่อมต่อกับคิวงานเพื่อมอบหมายและตรวจสอบงานบน codebase ของคุณ และให้มันตอบกลับความคิดเห็น PR โดยอัตโนมัติ
และคุณสามารถทำทั้งหมดนี้ได้โดยใช้โมเดล open weights ที่รวดเร็ว เพราะมันเป็นแค่โค้ด คุณไม่จำเป็นต้องมี LLM ที่ทรงพลังเพื่อคิดเกี่ยวกับ orchestration หลังจากที่มันถูกเขียนขึ้นครั้งแรก
ถึงเวลาสนุกแล้ว ถึงเวลาแชร์โปรแกรมบางส่วนที่เราใช้เพื่อเพิ่มผลผลิตอย่างมหาศาล
การวิจัย Codebase เชิงลึก (Deep Codebase Research)
เราใช้โปรแกรมนี้เพื่อช่วยกำหนดขอบเขตของงาน มันทำการวิจัยเชิงลึกเกี่ยวกับสถานะของ monorepo ของเรา และเตรียมชุดข้อมูลวิจัยสำหรับ implementer เพื่อใช้อ้างอิง เราใช้มันตลอดเวลา ฟังดูแพง แต่จริงๆ แล้วไม่ใช่ คุณสามารถรันโปรแกรมนี้ใน Slate ด้วย DeepSeek V4 Flash และกระบวนการวิจัยนั้นละเอียดแต่ถูกมาก

เป้าหมาย-ตรวจสอบ-PR (Goal-Review-PR)
อันนี้เป็นโปรแกรมที่เราใช้เพื่อทำงานให้สำเร็จเมื่อการวิจัยเสร็จสิ้น โชคดีที่เมื่อการวิจัยมาถึงโปรแกรมเป้าหมาย ความคลุมเครือของงานส่วนใหญ่ได้รับการแก้ไขแล้ว ทำให้การดำเนินงานเร็วขึ้นอีก การโหลดงานวิจัยล่วงหน้าด้วยโมเดล OSS ที่มีน้ำหนักเบาทำให้ง่ายสำหรับเราที่จะใช้โมเดลราคาแพงอย่าง Opus สำหรับสิ่งที่สำคัญ: การเขียนโค้ดที่ดีจริงๆ และตรวจสอบสถานะของระบบ คุณยังสามารถแก้ไขโปรแกรมให้ใช้ GPT 5.5 เพื่อตรวจสอบงานของ Opus 4.8 ในเชิง adversarial ได้

Autoresearch ในรูปแบบโปรแกรม
เดิมที Autoresearch[[9]](http://localhost:5173/blog/onyx#ref-9) ขับเคลื่อนโดย LLM ทั้งหมด สั่งให้ agent ไปที่ prompt program.md แล้วมันจะตัดสินใจว่าจะลองอะไรและจะดำเนินการอย่างไร
ไม่น่าแปลกใจเลย Autoresearch ก็เป็นแค่โปรแกรมจริงๆ
โปรแกรม agent ช่วยให้คุณกลับด้านนั้นและวางการควบคุมการไหล (control flow) ไว้ใน runtime โปรแกรมเป็นเจ้าของ control flow ในขณะที่ agent ทำงานที่มีผลข้างเคียง (แก้ไขโค้ด, รัน git, SSH ไปยัง GPU ระยะไกล, ฝึก) สำหรับโปรแกรม autoresearch การตัดสินใจ keep/revert เป็นโค้ดที่กำหนดได้:
1kept = status === "ok" && valBpb != null && valBpb < best
ในกรณีของเรา โปรแกรมจะรัน agent ตั้งค่าเพื่อเตรียม repo ใหม่และตรวจสอบว่า A100 ระยะไกลสามารถเข้าถึงได้ หากการตั้งค่าล้มเหลว มันจะส่งคืนก่อนกำหนดด้วยการออกที่สะอาดตามค่าที่พิมพ์ไว้ มิฉะนั้นจะเข้าสู่ลูปการทดลอง
การทดลองแต่ละครั้งจะได้ agent ใหม่ agent จะได้รับการกำหนดค่าที่ดีที่สุดในปัจจุบันและประวัติของแนวคิดและผลลัพธ์ก่อนหน้านี้ เพื่อที่มันจะไม่ทำซ้ำและสามารถต่อยอดจากสิ่งที่ถูกเก็บไว้ มันเสนอการเปลี่ยนแปลง, แก้ไข train.py, commit, rsync ไปยังเครื่องระยะไกล, ฝึก, และจำแนกผลลัพธ์
agent และโปรแกรมแบ่งปันสถานะ agent เขียนข้อมูลไปยังสถานะ และโปรแกรมประเมินสถานะสำหรับ control flow ตามผลลัพธ์ agent บันทึกจะอัปเดต results.tsv และอาจรีเซ็ตการรันหากโปรแกรมตัดสินใจทิ้งการทดลอง ซึ่งทำให้ git HEAD ชี้ไปยังสาขาที่ดีที่สุดในปัจจุบันของแผนผังการทดลองเสมอ
มีความแตกต่างหลักสองประการที่ควรให้ความสนใจ: 1) สิ่งนี้ทำงานในโปรแกรม ดังนั้นเราจึง สามารถ สร้าง agent ใหม่ต่อการทดลองหนึ่งครั้ง และ 2) เราสามารถตัดสินใจได้ว่างานใดที่ agent ควรทำตามสถานะโปรแกรมสด


และมันมีลักษณะเช่นนี้ในโค้ด:
1// ---------- โปรแกรม ----------23program(async (ctx) => {4 const c = cfg(ctx.input)5 const total = ctx.input?.maxExperiments ?? 2067 const setup = await run("ar-setup", {8 prompt: setupPrompt(c),9 type: "general",10 maxSteps: 40,11 output: SetupResult,12 })13 if (!setup.ready) {14 return { aborted: true, reason: `การตั้งค่าล้มเหลว: ${setup.note}`, setup }15 }1617 let best = c.baselineValBpb18 let bestCommit = setup.baselineCommit19 const history = []2021 for (let i = 1; i <= total; i++) {22 let exp23 try {24 exp = await run(`ar-exp-${i}`, {25 prompt: experimentPrompt(c, i, total, best, historyText(history)),26 type: "general",27 maxSteps: 80,28 output: ExperimentResult,29 })30 } catch (err) {31 // Agent เกิดข้อผิดพลาด/ถูกบล็อก — ถือว่าเป็น crash, คืนค่า repo ไปที่ best, ดำเนินการต่อ32 exp = {33 description: `agent การทดลอง ${i} เกิดข้อผิดพลาด`,34 commit: "error",35 status: "crash",36 valBpb: null,37 peakVramMb: null,38 numSteps: null,39 exitCode: -1,40 retries: 0,41 note: String(err?.message ?? err).slice(0, 200),42 }43 }4445 const kept = exp.status === "ok" && exp.valBpb != null && exp.valBpb < best4647 await run(`ar-record-${i}`, {48 prompt: recordPrompt(c, exp, kept, bestCommit),49 type: "general",50 maxSteps: 20,51 output: RecordResult,52 })5354 if (kept) {55 best = exp.valBpb56 bestCommit = exp.commit57 }5859 history.push({60 idx: i,61 description: exp.description,62 status: exp.status,63 valBpb: exp.valBpb,64 kept,65 commit: exp.commit,66 retries: exp.retries,67 })6869 await checkpoint({70 name: `experiment-${i}`,71 message: `exp ${i}/${total}: ${exp.status}${kept ? " เก็บไว้" : ""} val_bpb=${exp.valBpb ?? "n/a"} (best=${best})`,72 data: { i, total, status: exp.status, valBpb: exp.valBpb, kept, best, bestCommit },73 })74 }7576 const kepts = history.filter((h) => h.kept)77 return {78 baselineValBpb: c.baselineValBpb,79 bestValBpb: best,80 bestCommit,81 improvement: c.baselineValBpb - best,82 experimentsRun: history.length,83 kept: kepts.length,84 crashes: history.filter((h) => h.status === "crash").length,85 infraFails: history.filter((h) => h.status === "infra_fail").length,86 localRepo: c.localRepo,87 branch: c.branch,88 history,89 }90})
งานในอนาคต (Future work)
ข้อกำหนด VM ที่เหลือเพียงอย่างเดียวที่เรายังไม่ได้กำหนดคือโมเดลความทนทาน (durability model) สำหรับโปรแกรม ยังไม่ชัดเจนว่าโมเดลที่ถูกต้องสำหรับการ resume และจัดการวงจรชีวิตของโปรแกรมคืออะไร และควรเปิดเผยการควบคุมระดับใดบน runtime
นอกเหนือจากนั้น ยังมีสิ่งที่น่าตื่นเต้นอีกมากมายที่เราจะเพิ่มเพื่อรองรับปริมาณงานและรูปแบบงานที่แตกต่างกัน เพื่อที่เราจะสามารถเขียนซอฟต์แวร์จริงเพื่อ orchestrate agent ได้ดีขึ้น เรามั่นใจว่ารูปแบบต่างๆ มากมายจะเกิดขึ้นจากการที่ผู้คนใช้โปรแกรมในวิธีที่สร้างสรรค์ด้วยตัวเอง
เรารอไม่ไหวที่จะได้เห็นสิ่งที่คุณจะสร้าง
- ทีม RL
ข้อมูลอ้างอิง (References)
- Yao et al., "ReAct: Synergizing Reasoning and Acting in Language Models," 2022
- Geoffrey Huntley, "The Ralph Loop"
- Geoffrey Huntley, "everything is a ralph loop," January 2026
- Zhang, Kraska, Khattab, "Recursive Language Models," December 2025
- Wang et al., "Executable Code Actions Elicit Better LLM Agents," ICML 2024
- OpenAI, "Introducing Deep Research," February 2025
- Cursor, "Scaling Agents," January 2026
- OpenAI, "Using Goals in Codex"
- Andrej Karpathy, "autoresearch"
- Anthropic, "Introducing Dynamic Workflows in Claude Code"
- OpenAI, "Learning to Reason with LLMs," September 2024
- Anthropic, "A harness for every task: dynamic workflows in Claude Code"
- Random Labs, "Skill Chaining"





