som — orchestrator ที่ผมสร้างเพื่อสั่งทีม AI ทำงานวิศวกรรมจริง (ข้ามเครื่องได้) + เดโม
ผมสร้าง "som" — control plane สำหรับสั่ง AI agent หลายตัวให้ทำงานเป็นทีมจากที่เดียว คุมผ่านเว็บ/มือถือ และกระจายงานข้ามเครื่องได้ บทความนี้พาดูของจริง (มีวิดีโอ) พร้อมบทเรียนสถาปัตยกรรมที่เอาไปใช้ต่อได้
อ่าน ~10 นาที
som คืออะไร: control plane สำหรับสั่งทีม AI จากที่เดียว
ผมมี AI coding agent เก่งๆ อยู่หลายตัว (หลายค่าย) แต่ปัญหาคือมันอยู่กระจัดกระจาย — เปิดทีละ terminal, สั่งทีละตัว, ไม่รู้ว่าตัวไหนทำอะไรถึงไหน. ผมเลยสร้าง som ขึ้นมาเป็น "control plane" — ชั้นกลางที่คุม AI worker หลายตัวให้ทำงานเป็นทีมจากจุดเดียว สั่งผ่านเว็บหรือมือถือก็ได้.
แก่นทางเทคนิคเรียบง่ายกว่าที่คิด: มันคือ gateway (Node.js) ที่คุม terminal session จริงบนเครื่อง แล้วเปิด HTTP + WebSocket ให้หน้าเว็บ/แอปสั่งงานและดูจอสดได้. แต่ความยากไม่ได้อยู่ที่ตรงนั้น — มันอยู่ที่ "การรู้ความจริงของระบบ" และ "การส่งงานต่อกันไม่ให้พัง" ซึ่งเป็นบทเรียนหลักของบทความนี้.
Team Tree: มองทีมเป็น "ต้นไม้" ไม่ใช่ list
สิ่งแรกที่ผมเรียนคือ การมองทีม AI เป็น flat list พัง เพราะงานจริงมีลำดับชั้น. som จัดทีมเป็น ต้นไม้ (Team Tree) ที่มี 3 บทบาทชัดเจน:
- ROOT — ตัวสั่งการสูงสุด ตั้งทีม แจกโจทย์ ตรวจงานขั้นสุดท้าย
- MANAGER — คุมสายงานหนึ่ง (1 ชุดงาน = 1 สาย manager) แตกงานให้ worker
- WORKER — ลงมือทำจริง (เขียนโค้ด/รีวิว/ทดสอบ)
บทบาทไม่ใช่แค่ label — มันฝังใน "identity brief" ที่ agent แต่ละตัวได้รับ บอกว่ามันเป็นใคร ทำอะไรได้/ไม่ได้ และส่งงานให้ใครต่อ. บทเรียน transferable: ทีม (คนหรือ AI) ต้องมีโครงบทบาทชัดก่อน ไม่งั้นทุกตัวจะแย่งกันตัดสินใจหรือไม่มีใครตัดสินใจ.
Truth model: ของยากคือ "รู้ความจริง" ไม่ใช่การ generate
บทเรียนที่แพงที่สุดจากการทำ som: ตัวที่ AI เขียนโค้ดคือส่วนที่ง่าย — ของยากคือ "รู้ว่าตอนนี้เกิดอะไรขึ้นจริง". UI ต้องตอบผู้ใช้ให้ได้ 3 คำถามที่แยกจากกันเด็ดขาด ห้ามยุบเป็น boolean เดียว:
- process อยู่ไหม — session ยังมีชีวิตหรือตายแล้ว
- งานเดินไหม — process อยู่ ≠ งานคืบหน้า (อาจค้าง/รอ input/loop retry)
- gate ผ่านไหม — "รันเสร็จ" ≠ "ผ่านการตรวจ"
ความล้มเหลวส่วนใหญ่ที่ผมเจอตอนใช้งานจริง ไม่ได้มาจาก AI เขียนโค้ดห่วย — แต่มาจาก สถานะระบบที่ไม่สอดคล้องกัน: reviewer ตายก่อนได้ตรวจแล้วงานถูกตีเป็น "ไม่ผ่าน", infra กระตุกแล้วงานที่ถูกต้องถูกโยนทิ้งเพราะนับเป็น "แก้ไม่ผ่าน". หลักที่ยึด: แยก "งานพัง" ออกจาก "ระบบพัง" เสมอ — infra flake ห้ามทำลายหลักฐานงานที่ผ่านแล้ว. (เรื่องนี้ผมเขียนละเอียดในบทความ closed-loop engineering)
Mailbox + receipt: agent คุยกันด้วยข้อความที่ต้องมี ACK
agent ในทีมต้องส่งข้อความหากันได้ (worker รายงาน manager, manager รายงาน root). ตอนแรกผมทำเป็น fire-and-forget — พัง. ข้อความหายเงียบ, "ทีมไม่รายงานกลับ" กลายเป็นบั๊กที่ debug ยากสุด. ทางแก้คือ mailbox ที่ทุกข้อความต้องมี receipt/ACK และมี re-nudge เมื่อยังไม่ถูกอ่าน — แต่ต้อง throttle กันสแปม (หลาย worker ping พร้อมกันใน window เดียวทำให้ nudge โดน drop จนเมลค้าง).
// ข้อความระหว่าง agent = ต้องยืนยันรับ ไม่ใช่ยิงแล้วหวัง
mailbox.send({ to: "manager", from: "worker-3", body: report });
// ผู้รับต้อง ACK — ถ้าไม่อ่านใน window, re-nudge (แต่ throttle กันสแปม)
onTick(() => mailbox.reNudgeUnread({ throttleMs: 30_000 }));
บทเรียน: การสื่อสารในระบบกระจายต้อง at-least-once + idempotent + มี ACK — "ส่งแล้วคงถึง" คือสมมติฐานที่ผิดเสมอ.
ส่งงานข้ามเครื่อง: hub เก็บความจริง + satellite ที่ "โทรออกอย่างเดียว"
พอทีมโต เครื่องเดียวเอาไม่อยู่ ผมขยาย som ให้กระจาย worker ข้ามเครื่องได้ ด้วย 2 ส่วนที่หน้าที่ต่างกันชัด:
- hub — สมองของระบบ เก็บ "ความจริง" ทั้งหมด (สมุดงาน, mailbox, team tree, สถานะ) ที่เดียว
- satellite — ตัวเล็กๆ ที่ติดบนเครื่องแรงงานทุกเครื่อง แทบไม่มี state ของตัวเองเลย — หน้าที่เดียวคือรับคำสั่งจาก hub มารัน provider CLI (Claude/Codex/Qwen) บนเครื่องนั้น แล้ว stream จอกลับ
จุดที่ผมว่าเป็นหัวใจของดีไซน์นี้: satellite เป็นฝ่าย "โทรออก" หา hub ด้วย WebSocket ขาออกอย่างเดียว — ไม่เปิด port ขาเข้าเลยสักพอร์ต. นี่แก้ปัญหาใหญ่ที่สุดของการสั่งงานข้ามเครื่องในโลกจริง: เครื่องแรงงานมักอยู่หลัง NAT/firewall/router บ้าน ที่เปิด inbound port ไม่ได้ (หรือไม่ควรเปิด). พอ satellite เป็นฝ่ายเชื่อมออกไปเอง — ไม่ต้องแก้ firewall, ไม่ต้อง forward port, ไม่ต้องมี public IP — แค่เครื่องนั้นออกเน็ตได้ก็รับงานได้.
// satellite: ต่อออกหา hub แล้วรอรับคำสั่งบน connection เดิม (reverse dispatch)
const ws = new WebSocket(`${HUB_URL}/satellite`, { headers: { token } });
ws.on("message", async (cmd) => {
if (cmd.type === "spawn") {
const pty = launchProvider(cmd.provider, cmd.cwd); // รัน CLI บนเครื่องนี้
pty.onData((chunk) => ws.send({ nodeId: cmd.nodeId, screen: chunk })); // stream จอกลับ
}
});
// เครื่องนี้ไม่ listen อะไรเลย → NAT/firewall ไม่ต้องแก้
ประกอบกับการตัดสินใจอีก 2 ข้อ:
- VPN เป็น trust boundary — ทุกเครื่องอยู่ tailnet เดียวกัน; hub bind เฉพาะ loopback + VPN interface, ห้าม bind LAN และเทสว่ายิงจาก LAN แล้ว "ถูก refused จริง". satellite เก็บ token ไว้ (hub เก็บแค่ hash) เพิกถอนเครื่องได้ทันที
- co-location by branch — 1 งาน = 1 branch = 1 เครื่อง. งานที่เกี่ยวกันอยู่เครื่องเดียว (แชร์ working tree, commit branch เดียว) ไม่ต้อง sync ข้ามเครื่องระหว่างทำ. กระจายข้ามเครื่องที่ระดับ "งานที่ไม่เกี่ยวกัน" เท่านั้น
เพราะ satellite แทบไม่มี state — เครื่องแรงงานจึง "ทิ้งแล้วสร้างใหม่ได้" (disposable): ตาย/reboot แล้วต่อกลับมาก็รับงานต่อได้ เพราะความจริงอยู่ที่ hub เสมอ. นี่คือหลัก stateless worker ที่ทำให้ระบบทน failure ได้จริง.
ส่งงานต่อผ่าน "สมุดงาน" ไม่ใช่ shared disk
งานที่พึ่งพากันแต่คนละเครื่อง (B รอ API ของ A) ผมเลือกไม่แชร์ดิสก์ เพราะ mount ข้ามเครื่องมีราคาแฝงหนัก: latency ฆ่า npm/test (stat storm), watcher ไม่ข้าม mount, และแก้ไฟล์พร้อมกัน = last-write-wins. แทนที่ด้วย dependency handoff ผ่านสมุดงาน (job ledger) — งานประกาศว่าพึ่งงานไหน ระบบแจ้งเองเมื่อ dependency เสร็จและ commit แล้ว. หลักคิด: "ครึ่งเสร็จอย่าให้เห็น เสร็จแล้วระบบค่อยแจ้ง" — ส่งต่อด้วย git artifact (commit ref) ไม่ใช่ไฟล์ที่กำลังแก้ค้าง สถานะครึ่งๆ กลางๆ จึงไม่รั่วข้ามเครื่อง.
บทเรียนใหญ่: ของยากคือ orchestration semantics
สรุปภาพรวมจากการสร้าง som: คุณค่าไม่ได้อยู่ที่ "AI เขียนโค้ดได้" — มันอยู่ที่ระบบที่รู้ความจริง ส่งงานต่อไม่พัง และตรวจงานก่อนถือว่าเสร็จ. สิ่งที่กินแรงพัฒนา 80% คือ edge case ของ orchestration: session ตายกลางคัน, ข้อความหาย, reboot แล้ว console ฟื้นไม่ได้, quirk เฉพาะ OS ที่โผล่เฉพาะเครื่องจริง (Docker จำลองไม่ได้). พวกนี้แหละคือ "งานจริง" ของการทำ multi-agent system — ไม่ใช่ prompt engineering.
สรุป
- som = control plane คุม AI worker หลายตัวเป็นทีมจากเว็บ/มือถือ ผ่าน HTTP+WS บน terminal จริง
- Team Tree (root/manager/worker) — บทบาทชัดฝังใน identity brief
- truth model — แยก "process อยู่ / งานเดิน / gate ผ่าน" อย่ายุบเป็น boolean; แยกงานพังจากระบบพัง
- mailbox + ACK — สื่อสารต้อง at-least-once + idempotent + re-nudge (throttle กันสแปม)
- ข้ามเครื่อง = hub-and-spoke ผ่าน VPN — VPN เป็น trust boundary, ห้าม bind LAN, co-location by branch
- ส่งงานต่อผ่าน job ledger + git artifact ไม่ใช่ shared disk
- ของยากคือ orchestration semantics ไม่ใช่ตัว generate




