{"slug": "harness-engineering-101-singthii-yuuaitphrmkh-ng-agentic-ai", "title": "Harness Engineering 101 — สิ่งที่อยู่ใต้พรมของ Agentic AI", "summary": "Harness engineering is the discipline of building the scaffolding that wraps an LLM and manages failures so that agents can work in an imperfect world. A developer recounts how an agent searching for condos on a Thai auction site failed because the tool returned 'clicked' before the page finished submitting, requiring verification via browser_snapshot. The harness also handles context window growth by compressing old tool results, and implements adaptive retry strategies that change tactics on each attempt, such as using button.click() instead of form.submit() to avoid CAPTCHA race conditions.", "body_md": "บทความก่อนเราคุยกันเรื่อง \"จาก LLM เปล่า → Agentic AI\" แบบ 7 layer\n\nคราวนี้มาดูว่าภายในแต่ละ layer มันทำงานยังไง — และอะไรที่พังได้บ้าง\n\nเวลาเราใช้ Claude Code, Cursor, หรือ Hermes — เราเห็น AI ทำงานเป็นขั้นตอน:\n\n```\nคิด → เรียก tool → ดูผล → คิดต่อ → เรียก tool → เสร็จ\n```\n\nสิ่งที่เราไม่เห็นคือ **ทุกอย่างที่พังระหว่างทาง** — และมีคน (หรือโค้ด) ที่คอยจัดการความพังพวกนั้นอยู่ตลอดเวลา\n\nนั่นแหละคือ **Harness Engineering** — ศาสตร์แห่งการสร้าง \"โครง\" ที่ห่อหุ้ม LLM ไว้ แล้วจัดการทุกอย่างให้ agent ทำงานได้จริงในโลกที่ไม่มีอะไร perfect\n\nนี่คือ loop ที่ทุก agent รัน:\n\n```\nwhile not done and budget_ok:\n    response = llm.chat(messages, tools)\n\n    if response.has_tool_calls():\n        for tool in response.tool_calls:\n            result = execute_tool(tool)\n            messages.append(result)\n    else:\n        return response.text\n```\n\nดูเผิน ๆ เหมือน while loop ธรรมดา — แต่นี่คือที่ที่ทุกอย่างพังได้\n\nครั้งหนึ่งผมให้ Hermes หาคอนโดในเว็บ LED (เว็บประมูลทรัพย์ของกรมบังคับคดี)\n\nAI วางแผน: เข้าเว็บ → กรอกฟอร์ม → กด submit → อ่านผล\n\nAI เรียก tool `browser_click(ref=\"submit_button\")`\n\n— tool return ว่า \"clicked\"\n\nAI ดีใจ — \"เรียบร้อย! ได้ผลลัพธ์แล้ว\" — แล้วพยายามอ่านผลลัพธ์จากหน้าที่ไม่โหลดขึ้นมาจริง\n\n**เกิดอะไรขึ้น?** Tool return \"clicked\" แต่หน้าเว็บยัง submit ไม่เสร็จ — JavaScript ยังทำงาน, DOM ยังไม่เปลี่ยน, CAPTCHA ยังไม่ validate\n\n**Harness ต้องจัดการ:** หลังจาก `browser_click`\n\nต้องมี `browser_snapshot`\n\nเพื่อยืนยันว่าหน้าเว็บเปลี่ยนจริง — และถ้าหน้าไม่เปลี่ยน ต้อง retry หรือเปลี่ยนกลยุทธ์\n\nนี่คือสิ่งที่ harness ทำ — **มันไม่เชื่อ tool call ทันที แต่มัน verify**\n\nทุกครั้งที่ AI เรียก tool — context window จะยาวขึ้น เพราะต้องเก็บ:\n\n```\n[user message] → [assistant tool_call] → [tool result] → [assistant tool_call] → [tool result] → ...\n```\n\nถ้า AI ทำงาน 50 รอบ — context อาจยาวถึง 100K+ tokens\n\n**ปัญหา:**\n\n**Harness ต้องจัดการ:**\n\n```\nif token_count > threshold:\n    compress_context()  # ตัด tool result เก่า ๆ ออก เหลือแต่ใจความ\n```\n\nเรื่องเล่า: เคยมีครั้งนึง ในทีมที่ดูแล agent ตัวหนึ่ง — AI ทำงานนาน 50+ tool calls — context ยาว 150K tokens — มันเริ่มวน loop: อ่านไฟล์ซ้ำ, แก้แล้วแก้อีก, ลืมว่าตัวเองทำอะไรไปแล้ว — harness ตัดสินใจ compress context อัตโนมัติ — เหลือ 30K tokens — AI กลับมามีสติและทำงานต่อได้ทันที\n\nTool call ไม่ได้สำเร็จเสมอไป:\n\n```\n- terminal(\"git push\"): Permission denied\n- browser_click(\"submit\"): Page did not change\n- web_search(\"Go 1.27\"): CAPTCHA blocked\n- read_file(\"config.yaml\"): File not found\n```\n\nAI ต้องรู้ว่า tool ล้มเหลว — และต้องมี **กลยุทธ์กู้คืน**\n\nHermes ต้องค้นหาทรัพย์ในเว็บ LED — ครั้งแรกทำตามปกติ: กรอกฟอร์ม → กด submit → **CAPTCHA block**\n\nHarness pattern ที่ใช้จริง:\n\n```\nAttempt 1: form.submit() → CAPTCHA block ❌\nAttempt 2: อ่าน CAPTCHA ก่อน → submit → CAPTCHA เปลี่ยนระหว่าง submit ❌  \nAttempt 3: กรอกข้อมูลในฟอร์มทุกช่องแบบเงียบ ๆ — ใช้ JavaScript ใส่ค่าลงใน input field โดยตรง โดยไม่ให้เว็บรู้ว่ากำลังมีคนกรอก (ไม่ trigger `onChange` event เพราะ event พวกนั้นจะไปเรียก AJAX โหลดข้อมูลอำเภอ ซึ่งทำให้ CAPTCHA รีเฟรชก่อน submit) → อ่าน CAPTCHA เป็นขั้นตอนสุดท้าย → กดปุ่ม submit ด้วย `button.click()` แทน `form.submit()` → ✅ สำเร็จ!\n```\n\nHarness ไม่ได้แค่ \"retry\" แบบโง่ ๆ — มันเปลี่ยนกลยุทธ์ในแต่ละครั้ง:\n\n| Attempt | กลยุทธ์ | ผล |\n|---|---|---|\n| 1 | form.submit() | ❌ CAPTCHA |\n| 2 | CAPTCHA ก่อน submit | ❌ race condition |\n| 3 | button.click() + CAPTCHA last | ✅ |\n\nนี่คือ **adaptive retry** — ไม่ใช่แค่เรียกซ้ำด้วย parameter เดิม\n\nสมมติ AI เรียก `read_file(\"main.go\")`\n\n— ได้โค้ด 500 บรรทัด\n\nใน loop ถัดไป AI อ่านอีก 3 ไฟล์, รัน test, แก้โค้ด, รัน test อีก — context ยาวขึ้นเรื่อย ๆ\n\nแต่ AI ไม่จำเป็นต้อง \"จำ\" เนื้อหาทั้ง 500 บรรทัดของ `main.go`\n\nในรอบที่ 10 — มันอาจต้องรู้แค่ \"main.go มี function main ที่เรียก RunServer\"\n\n**Harness ต้องตัด:**\n\n```\n📄 main.go (500 lines) — รอบที่ 1: เก็บหมด\n📄 main.go (500 lines) — รอบที่ 10: เก็บเฉพาะ summary \"defines main(), calls RunServer()\"\n```\n\nเทคนิคนี้เรียกว่า **context compaction** — harness ใช้ LLM ตัวเล็ก (ถูก) อ่าน tool result เก่า ๆ → สรุป → เก็บเฉพาะ summary\n\nเรื่องจริง: Hermes มี\n\n`compression.threshold`\n\nใน config — default 0.50 (50% ของ context window) — พอ context เกินครึ่ง มันจะ compress อัตโนมัติ — จาก 100K → 20K tokens —ประหยัดเงินไป $0.24 ต่อ API call\n\nLLM มีกฏตายตัว: **ข้อความใน conversation ต้องสลับ role กัน**\n\n```\n✅ user → assistant → user → assistant → user\n❌ user → assistant → assistant → user    ← พัง!\n```\n\nฟังดูง่าย — แต่ใน agent loop มันไม่ง่ายเลย:\n\n```\n[user: \"สร้าง API\"]\n[assistant: tool_call read_file]   ← assistant\n[tool result: \"ไฟล์มี 200 บรรทัด\"]  ← tool (ไม่ใช่ user ไม่ใช่ assistant)\n[assistant: tool_call write_file]  ← assistant ซ้อน assistant! ❌\n```\n\n**Harness ต้องแก้:** รวม tool calls ที่ต่อเนื่องกันเข้าเป็น assistant message เดียว\n\n```\n# แทนที่จะส่งทีละ tool call — harness รวบก่อนส่ง\nmessages = [\n    {\"role\": \"user\", \"content\": \"สร้าง API\"},\n    {\"role\": \"assistant\", \"tool_calls\": [read_file, write_file, run_test]},  # รวม 3 calls\n    {\"role\": \"tool\", \"results\": [...]},\n    {\"role\": \"assistant\", \"content\": \"เสร็จแล้วครับ\"}\n]\n```\n\nเรื่องจริง: มี bug ใน Hermes เวอร์ชันก่อนที่\n\n`role alternation`\n\nพังตอน`/stop`\n\n— AI กำลังเรียก tool แล้ว user กด stop — harness ไม่ได้รวบ tool calls ที่ค้างอยู่ → ส่ง assistant สองครั้งติด → API error — ใช้เวลา debug 3 ชั่วโมงถึงเจอ\n\nเรามี function ใน Python:\n\n``` php\ndef read_file(path: str, offset: int = 1, limit: int = 500) -> dict:\n    \"\"\"Read a text file with line numbers.\"\"\"\n```\n\nLLM ไม่เข้าใจ Python — harness ต้องแปลงเป็น JSON schema:\n\n```\n{\n  \"name\": \"read_file\",\n  \"description\": \"Read a text file with line numbers.\",\n  \"parameters\": {\n    \"path\": {\"type\": \"string\", \"description\": \"Path to the file\"},\n    \"offset\": {\"type\": \"integer\", \"default\": 1},\n    \"limit\": {\"type\": \"integer\", \"default\": 500}\n  }\n}\n```\n\nและเมื่อ LLM ตอบกลับมา:\n\n```\n{\"name\": \"read_file\", \"arguments\": {\"path\": \"/home/user/main.go\"}}\n```\n\nHarness ต้องแปลงกลับเป็น `read_file(path=\"/home/user/main.go\")`\n\n— แล้วเรียกจริง\n\nฟังดู trivial — แต่เวลามี 50 tools, แต่ละตัวมี parameter 5-10 ตัว — harness จัดการ schema ทั้งหมดนี้ให้เราโดยที่เราไม่ต้องคิด\n\nผู้ใช้เปลี่ยนใจกลางทาง: \"หยุด! ไม่เอาอันนั้นแล้ว\"\n\nHarness ต้อง:\n\n`terminal`\n\nที่รัน build อยู่)ตัวอย่าง: ผู้ใช้สั่ง\n\n`/stop`\n\nตอน AI กำลัง clone repo ขนาดใหญ่ —`git clone`\n\nรันไป 80% แล้ว — harness ต้องฆ่า process, cleanup ไฟล์ที่ clone มาแล้วบางส่วน, แล้วกลับมาพร้อมตอบ — ทั้งหมดนี้ในเวลา < 1 วิ\n\nสรุป: Harness Engineering คือ **วิศวกรรมซอฟต์แวร์ธรรมดา** ที่ถูกออกแบบมาเพื่อจัดการกับ LLM ที่ไม่ธรรมดา\n\n| ปัญหา | วิธีแก้ | ไม่ใช่ AI — คือ Engineering |\n|---|---|---|\n| Context ยาวเกิน | Compaction | จัดการ memory |\n| Tool call พัง | Retry + adaptive strategy | Error handling |\n| Role ซ้ำ | Merge tool calls | Message routing |\n| Schema ไม่ตรง | Auto-generate JSON schema | Serialization |\n| User สั่ง stop | Graceful interrupt | Process management |\n\nLLM คือสมอง — Harness คือระบบประสาท กล้ามเนื้อ และภูมิคุ้มกันที่ห่อหุ้มสมองนั้นอยู่\n\nอยากลองสร้าง harness ของตัวเอง?\n\n``` python\n# harness ขั้นต่ำ — 30 บรรทัด\ndef run_agent(user_input, tools, max_rounds=10):\n    messages = [{\"role\": \"user\", \"content\": user_input}]\n\n    for _ in range(max_rounds):\n        response = llm.chat(messages, tools)\n\n        if not response.has_tool_calls():\n            return response.text  # ✅ จบ\n\n        # รวบทุก tool call เป็น message เดียว (role alternation)\n        tool_results = []\n        for tc in response.tool_calls:\n            result = execute(tc.name, tc.args)\n            tool_results.append(result)\n\n        messages.append({\"role\": \"assistant\", \"tool_calls\": response.tool_calls})\n        messages.append({\"role\": \"tool\", \"results\": tool_results})\n\n        # Token budget — compress ถ้าเกิน 80%\n        if count_tokens(messages) > 80000:\n            messages = compress(messages)\n\n    return \"Max rounds exceeded\"\n```\n\nจาก 30 บรรทัดนี้ — คุณจะเจอปัญหาเดียวกับที่ Kiro, Antigravity, Hermes เจอ — และนั่นคือจุดเริ่มต้นของ Harness Engineering\n\n📚\n\nอ่านต่อ:", "url": "https://wpnews.pro/news/harness-engineering-101-singthii-yuuaitphrmkh-ng-agentic-ai", "canonical_source": "https://dev.to/gophernment/harness-engineering-101-singthiiyuuaitphrmkhng-agentic-ai-50nk", "published_at": "2026-07-01 02:12:00+00:00", "updated_at": "2026-07-01 02:18:51.386668+00:00", "lang": "en", "topics": ["ai-agents", "large-language-models", "developer-tools"], "entities": ["Claude Code", "Cursor", "Hermes", "LED", "CAPTCHA"], "alternates": {"html": "https://wpnews.pro/news/harness-engineering-101-singthii-yuuaitphrmkh-ng-agentic-ai", "markdown": "https://wpnews.pro/news/harness-engineering-101-singthii-yuuaitphrmkh-ng-agentic-ai.md", "text": "https://wpnews.pro/news/harness-engineering-101-singthii-yuuaitphrmkh-ng-agentic-ai.txt", "jsonld": "https://wpnews.pro/news/harness-engineering-101-singthii-yuuaitphrmkh-ng-agentic-ai.jsonld"}}