ข้ามไปยังเนื้อหา

วงจรชีวิตการสั่งงานเอเจนต์

การสั่งงานคือการรันหนึ่งครั้งของเอเจนต์หนึ่งตัว ในบทสนทนาหนึ่งบท บนอะแดปเตอร์หนึ่งตัว ทุกอย่างที่เกี่ยวกับมันเกิดขึ้นบน cc_server: ไคลเอนต์ส่งข้อความแล้วเฝ้าดูเหตุการณ์ที่สตรีมกลับมา ไม่มีเส้นทางการสั่งงานอยู่ในไคลเอนต์ใดเลย

เอเจนต์ถูกปลุกด้วยเหตุผลหกประการ ซึ่งการรันจะพกติดไปเป็น บริบทการปลุก เพื่อให้เอเจนต์รู้ว่า_ทำไม_มันจึงถูกปลุกขึ้นมา:

เหตุผลการปลุก ตัวกระตุ้น
userMessage มีคนส่งข้อความในสเปซ หรือกล่าวถึงเอเจนต์
assignment มีตั๋วงานถูกมอบหมายให้เอเจนต์
pipelineStep ขั้นตอนไปป์ไลน์สั่งงานมัน
childCompleted ตั๋วงานลูกทำงานเสร็จ
followUp ฮาร์ตบีตติดตามผลตามกำหนดเวลา
recovery เส้นทางการกู้คืนสั่งงานซ้ำหลังจากเกิดความล้มเหลว

บริบทการปลุกพา id ต่าง ๆ ที่มาพร้อมเหตุผลนั้นไปด้วย — ตั๋วงาน, สเปซ, ข้อความ, รันไปป์ไลน์ — และ id เหล่านั้นถูกฉีดเข้าไปในสภาพแวดล้อมและพรอมต์ของเอเจนต์

พรอมต์ถูกสร้างขึ้นเป็นชั้น ๆ โดยแต่ละชั้นเป็นส่วนที่มีป้ายกำกับ:

  • ตัวตน — id, ชื่อ และ id ของเวิร์กสเปซของเอเจนต์เอง พร้อมคำสั่งชัดเจนให้ส่ง UUID เหล่านั้นแบบตรงตัวไปยังเครื่องมือที่จำกัดขอบเขตตามเวิร์กสเปซ เวิร์กสเปซเป็นส่วนที่ฝังอยู่ในตัวเอเจนต์เอง จึงมีอยู่เสมอ
  • แค็ตตาล็อกเครื่องมือและโปรโตคอลทรัพยากร จากนั้นเป็น โครงร่างของเวิร์กสเปซ
  • พรอมต์ระบบที่เขียนทับ หากเอเจนต์มี จากนั้นเป็น เพอร์โซนา (กำหนดพารามิเตอร์ตามบทบาท) และ บรีฟของทีม
  • สกิล — ชื่อต่าง ๆ ที่ผูกอยู่กับเอเจนต์
  • สัญญาการดำเนินงานและขั้นตอนปฏิบัติ สำหรับโหมดปัจจุบัน
  • บริบทการปลุก และ บริบทการกล่าวถึง: ใครเป็นผู้เรียกเอเจนต์ และรายชื่อผู้เข้าร่วมที่อยู่รอบตัวมัน
  • บล็อกโหมด — สร้างขึ้นจากโปรไฟล์ขีดความสามารถของโหมด และรายการเครื่องมือที่ถูกทำให้เกิดขึ้นจริง ดังนั้นพรอมต์จึงไม่มีทางระบุชื่อเครื่องมือที่การรันไม่มีอยู่จริงได้
  • บริบทหน่วยความจำ — ข้อเท็จจริงที่ถูกดึงมาสำหรับงานนั้น
  • บริบทการสนทนา — ประวัติสเปซล่าสุด จำกัดไว้ที่หน้าต่างข้อความแบบคำต่อคำ 50,000 ตัวอักษร โดยไม่สนใจขนาดบริบทที่กำหนดค่าไว้ของเอเจนต์ ประวัติที่เก่ากว่าจะไม่ถูกตัดทิ้ง: เอเจนต์ดึงมันมาเมื่อต้องการด้วย get_messages และข้อความเก่าที่เกี่ยวข้องเชิงความหมายก็ยังปรากฏขึ้นได้

คำสั่งถูกแยกจัดการในสองที่ /todo และ /compact ถูกดักจับในช่องเขียนข้อความ และจงใจไม่ถูกบันทึกเป็นข้อความหรือส่งไปยังเอเจนต์เลย — การบันทึก /compact จะทำให้จุดตัดของการย่อประวัติถอยกลับไปหนึ่งรอบและปนเปื้อนทรานสคริปต์ /plan ถูกจัดการทั้งสองที่: ช่องเขียนข้อความสลับโหมดของสเปซ ซึ่งเป็นสิ่งที่ทำให้ชุดเครื่องมือ, พรีเซ็ตของกฎคุม และพรอมต์สอดคล้องกันสำหรับรอบนี้และทุกรอบถัดไป /goal, /loop และ /skill:<name> ถูกจัดการภายในตัวการรันเอง และเฉพาะบนฮาร์เนสในตัวเท่านั้น — /goal และ /loop เริ่มงานอัตโนมัติด้วยงบประมาณต้นทุน และชื่อที่ขึ้นต้นด้วย skill: จะถูกค้นหาเป็นสกิลที่เนื้อหา SKILL.md กลายเป็นคำสั่งกำกับของรอบนั้น เนมสเปซคือสิ่งที่แยกคำศัพท์สองชุดนี้ออกจากกัน: การแก้ชื่อเปล่าเทียบกับสกิลหมายความว่าสกิลชื่อ plan หรือ compact ถูกบดบังโดยคำสั่งในตัวอย่างถาวร ชื่อเปล่าที่ไม่ใช่คำสั่งในตัวยังคงถูกแก้ได้ สำหรับข้อความที่เขียนไว้ก่อนมีเนมสเปซ ชื่อที่ไม่รู้จักก็จะถูกส่งผ่านไปเป็นข้อความธรรมดา

ก่อนที่อะไรจะเริ่มรัน บทสนทนาจะได้รูทการทำงานของตัวเอง พร้อม worktree แบบคัดลอกเมื่อเขียนที่แยกออกจากกันหนึ่งชุดต่อหนึ่งรีโปภายใต้รูทนั้น โดยค่าเริ่มต้นคือทุกรีโปที่เชื่อมกับเวิร์กสเปซ สเปซของ PR — หรือสเปซที่สร้างขึ้นพร้อมการเลือกอย่างชัดเจน — จะจัดเตรียมเฉพาะรีโปที่อยู่ในรายการที่อนุญาต และบทสนทนารีวิว PR จะตรึงรีโปของตนเพียงหนึ่งเดียวไว้ที่ head ref ของ pull request ขณะที่รีโปอื่น ๆ อยู่บนสาขาเริ่มต้นของตน เช็กเอาต์ต้นทางไม่ถูกแก้ไขเลย และไดเรกทอรีบทสนทนาทั้งหมดจะถูกเรียกคืนเมื่อสเปซถูกลบ

การคัดลอกเมื่อเขียนเป็นวิธีใช้งานเดียวในทุกที่ที่มี rift มาด้วย โดยหนุนด้วยเนทีฟที่บันเดิลมา ระบบไฟล์ที่ไม่สามารถ reflink ได้ — หรือไดเรกทอรีข้อมูลที่อยู่คนละวอลุ่มกับรีโป — จะทำให้การจัดเตรียมล้มเหลวแทนที่จะถอยไปใช้วิธีสำรอง git worktree เป็นแบ็กเอนด์บน Windows ซึ่งไม่มีแบ็กเอนด์การคัดลอกเมื่อเขียนให้สร้าง และมันไม่ใช่การลดระดับแบบเงียบ ๆ บนโฮสต์อื่น การที่ rift หายไปบนแพลตฟอร์มที่มีมันมาด้วยถือเป็นการติดตั้งที่พัง — เซิร์ฟเวอร์จะปฏิเสธการบูตแทนที่จะถอยกลับอย่างเงียบ ๆ เพราะการถอยกลับแบบเงียบจะซ่อนความเสียหายไว้เบื้องหลังเส้นทางที่ช้ากว่าตลอดไป

ตัวตนสองแบบถูกกำหนดต่อการรันหนึ่งครั้ง ก่อนที่โปรเซสใดจะเริ่มรัน

ความเป็นผู้เขียนของ Git คอมมิตที่เอเจนต์สร้างจะถูกระบุผู้เขียน เป็นตัวเอเจนต์เอง — <Name> (agent) <agentId@agents.control-center.local> ทั้งใน GIT_AUTHOR_* และ GIT_COMMITTER_* — ไม่ปลอมตัวเป็นมนุษย์เลย ส่วนหน้าเครื่องหมาย @ คือ UUID ของเอเจนต์ ไม่ใช่ slug ของชื่อมัน มนุษย์ผู้ร้องขอจะได้รับเครดิตผ่านบรรทัดท้าย Co-Authored-By: ซึ่งพกไปในสภาพแวดล้อมและสั่งกำกับไว้ในพรอมต์ระบบ ตัวตนที่ผู้เรียกระบุไว้อย่างชัดเจนจะชนะทั้งสองแบบเสมอ

ตัวตนบนโฮสต์โค้ด ข้อมูลรับรอง GitHub ของการรันเป็นของโบรกเกอร์เท่านั้นเสมอ — โทเคนการติดตั้งของ App ที่จำกัดขอบเขตตามรีโป หรือ PAT สำรองจากสภาพแวดล้อมของมัน — ไม่ใช่โทเคนที่จัดเก็บไว้ของสมาชิกผู้ร้องขอเอง actingUserId จำกัดขอบเขตของ PAT สำรอง เพื่อให้สมาชิกที่ไม่ได้เป็นเจ้าของเซิร์ฟเวอร์ไม่ได้รับช่วงการเข้าถึงของเซิร์ฟเวอร์ไปด้วย มันไม่ได้ซ้อนทับโทเคนส่วนตัวลงบนสภาพแวดล้อมของเอเจนต์

ข้อมูลรับรองของผู้ให้บริการโมเดล ถูกกำหนดตามลำดับที่แน่นอน และลำดับนั้นสำคัญ:

  1. การเขียนทับสภาพแวดล้อมรายอะแดปเตอร์
  2. สภาพแวดล้อมของผู้เรียก
  3. ที่เก็บข้อมูลรับรองของเซิร์ฟเวอร์ (รีเฟรชโทเคน OAuth หากใกล้หมดอายุ)
  4. สภาพแวดล้อมของโปรเซส

เนื่องจากการเขียนทับรายอะแดปเตอร์มาเป็นอันดับแรก มันจึงชนะบัญชีผู้ให้บริการที่เชื่อมต่อไว้ในแอปอย่างเงียบ ๆ หากการรันเรียกเก็บเงินผิดบัญชี การเขียนทับนั้นคือที่แรกที่ควรตรวจดู ข้อมูลรับรองของผู้ให้บริการเป็นระดับโฮสต์ทั้งโฮสต์ — ใช้ร่วมกันในทุกเวิร์กสเปซของระบบที่ติดตั้งนี้

โทเคน GitHub ของการรันมาจากโบรกเกอร์ข้อมูลรับรอง: โทเคนแบบอ่านอย่างเดียวที่จำกัดขอบเขตกับรีโปของ worktree การพุชผ่านเกตเวย์การรันเอเจนต์ ซึ่งถามกฎ พุชไปยังรีโมต ก่อนส่งต่อ และไม่มีการฉีดคีย์ตั๋วงานใด ๆ ดูแซนด์บ็อกซ์และความปลอดภัย

การรันถูกเปิดขึ้นโดยมี worktree ของมันเป็นไดเรกทอรีทำงาน และ — สำหรับ Claude Code — มีคอนฟิก MCP ที่สร้างขึ้น เพื่อให้เอเจนต์เข้าถึงชุดเครื่องมือของ Control Center ได้ ACP และ bash ของฮาร์เนสจะได้สภาพแวดล้อมที่ถูกทำให้สะอาด ส่วน Claude Code ผ่าน NoSandboxAdapter จะสืบทอดสภาพแวดล้อมของโฮสต์

การรันสตรีมลำดับเหตุการณ์ที่มีชนิดข้อมูลกลับมา ซึ่งตัวประมวลผลสตรีมจะบันทึกเป็นเดลตาของข้อความ พับรวมเป็นเซกเมนต์ของทรานสคริปต์ และแปลงเป็นเหตุการณ์การส่งข้อความ:

เหตุการณ์ สิ่งที่มันนำมาด้วย
thinking เอาต์พุตการให้เหตุผล แสดงแบบอินไลน์
text เอาต์พุตที่มองเห็นได้
toolCall การเรียกใช้เครื่องมือ พร้อม id และอาร์กิวเมนต์เชิงโครงสร้าง
toolResult ผลลัพธ์ที่ตรงกัน พร้อมแฟล็กเมื่อเกิดข้อผิดพลาดหรือเป็นสตรีมบางส่วน
usage จำนวนโทเคนและต้นทุนสำหรับช่วงการวัด
error ข้อผิดพลาด พร้อมรหัสเชิงโครงสร้างของอะแดปเตอร์เมื่อมีการรายงาน
sandboxViolation ระบบปฏิบัติการปฏิเสธการเรียกใช้ระบบ จะถูกส่งออกเฉพาะเมื่อมีแซนด์บ็อกซ์ห่อหุ้มโปรเซสอยู่จริงเท่านั้น — ดูหมายเหตุด้านบน
debug การวินิจฉัยขณะรัน (เครื่องหมายการเริ่มรัน การตัดสินใจของกฎคุม การกระตุ้นตามสัญญา)
done การรันเสร็จสิ้น

จงใจให้ไม่มีเพดานจำนวนรอบในที่ใดเลย การนับจำนวนรอบแบบตายตัวจะฆ่างานยาวที่ชอบธรรม (การไมเกรต, การเก็บกวาดงานแบบกระจาย) และไม่ได้จับอะไรที่ตัวป้องกันด้านล่างจับได้เร็วกว่าเลย การรันสิ้นสุดลงเมื่อ:

  • โมเดลหยุดเอง
  • มนุษย์หยุดมัน หรือเข้าควบคุมบทสนทนา (ซึ่งจะหยุดฮาร์เนสในตัวชั่วคราว ณ ขอบเขตรอบที่สะอาดรอบถัดไปของมัน — ส่วนการรันของ Claude Code ซึ่งไม่มีขอบเขตที่ปลอดภัย จะถูกหยุดแทน)
  • ตัวเฝ้าระวังความเงียบทำงาน หากไม่มีเอาต์พุตเกินเกณฑ์ที่กำหนด เซสชันจะถูกยุติด้วยความล้มเหลว silentRun ค่าเริ่มต้นคือ 15 นาที ลดลงเหลือ 10 นาทีในโหมดที่อ่านเป็นหลัก (รีวิวและแผน) เอเจนต์อาจเขียนทับค่านี้ได้ระหว่าง 1 ถึง 240 นาที ตัวเฝ้าระวังจะถูกติดตั้งเมื่อโปรเซสลูกรายงาน PID ของมัน ดังนั้นมันจึงครอบคลุม Claude Code CLI — ไม่ใช่ฮาร์เนสในตัว ซึ่งไม่ได้เปิดโปรเซสใดเลย
  • เพดานค่าใช้จ่ายเข้าขบ — แต่เฉพาะกับคำสั่งแบบอัตโนมัติเท่านั้น

ตัวป้องกันการวนซ้ำซากไม่ใช่ตัวสั่งยุติ: การเรียกเครื่องมือที่เหมือนกันสามครั้งติดต่อกัน แล้วลูปจะ_บังคับทิศทาง_ให้เอเจนต์เปลี่ยนแนวทาง

เมื่อนำมารวมกัน รอบแบบอินเทอร์แอกทีฟธรรมดาบนฮาร์เนสในตัวจะไม่มีเพดานจำนวนรอบ ไม่มีเพดานค่าใช้จ่าย และไม่มีตัวเฝ้าระวังความเงียบ มันจบลงเมื่อโมเดลหยุดหรือเมื่อคุณสั่งให้หยุด นั่นเป็นการแลกเปลี่ยนที่จงใจ — ทางเลือกอื่นล้วนฆ่างานยาวที่ชอบธรรม — แต่มันก็หมายความว่าการรันแบบอินเทอร์แอกทีฟที่ไม่มีคนเฝ้าจะมีเพียงวิจารณญาณของโมเดลเองเป็นขอบเขต

ระหว่างการรัน ข้อความที่ส่งไปยังเอเจนต์ที่กำลังทำงานจะถูกจัดคิวเป็นการบังคับทิศทาง และถูกดึงออกไปที่ขอบเขตรอบถัดไป คุณจึงสามารถกระตุ้นการรันโดยไม่ต้องฆ่ามันได้ การบังคับทิศทาง การหยุดชั่วคราว เอเจนต์ย่อย และสัญญาการทำงานให้เสร็จเป็นคุณสมบัติของฮาร์เนสในตัว; Claude Code ไม่มีสิ่งเหล่านี้เลย

การสั่งงานทุกครั้งเขียน AgentRunLog: เวลาเริ่มและสิ้นสุด, สรุป, จำนวนโทเคนและต้นทุนโดยประมาณ (รวมถึงต้นทุนที่รวบรวมมาจากเอเจนต์ย่อย), อะแดปเตอร์และโมเดล, process id และเส้นทางบันทึก, บทสนทนา และตั๋วงานหรือรันไปป์ไลน์ใด ๆ ที่มันสังกัดอยู่ พร้อมด้วยฟิลด์การจำแนกสองฟิลด์ที่บอกความจริงส่วนใหญ่ไว้

  • สถานะ เป็นแบบหยาบ: pending, running, completed, error
  • ความมีชีวิต เป็นการอ่านที่ละเอียดกว่า: alive, productive, completed, blocked, empty, looping, failed, stalled, dead
  • ตระกูลข้อผิดพลาด จัดกลุ่มความล้มเหลวได้อย่างมีประโยชน์: transientUpstream, sandboxInfrastructure, budgetExceeded, processLost, silentRun, unknown

การปิดการรันจะเผยแพร่ AgentRunCompleted และมีอะไรอีกมากที่ขึ้นอยู่กับมัน: การรวบรวมต้นทุนของไปป์ไลน์, การทำงานต่อของขั้นตอนไปป์ไลน์ที่รอการรันนี้อยู่, การปิดตั๋วงานที่รองรับ, การแมปการประสานงานไปยังสถานะสิ้นสุด, การตัดสินใจว่าจะทำงานต่อหรือหยุดของผู้กำกับเป้าหมาย, บทบาทผู้ตรวจสอบ และการประเมินงบประมาณหลังการรัน ทำเนียบสถานะออนไลน์จะตามมาภายหลังแยกต่างหาก โดยเฝ้าดูบันทึกการรันล่าสุดแทนที่จะดูเหตุการณ์ การแจ้งเตือน “เอเจนต์เสร็จแล้ว” ไม่ได้อยู่ในรายการนั้น: เหตุการณ์นี้เกิดขึ้นเมื่อสตรีมของโปรเซสสิ้นสุดลง ก่อนที่คำตอบจะถูกเขียน การแจ้งเตือนจึงเป็น MessageReceived ของเทิร์นนั้น ซึ่งเผยแพร่เมื่อข้อความถูกบันทึกถาวรแล้ว

การบังคับใช้งบประมาณเป็น_ปฏิกิริยา_ ไม่ใช่ประตูด่าน เมื่อการรันเสร็จสิ้น ตัวประเมินจะเทียบค่าใช้จ่ายตั้งแต่ต้นเดือนถึงปัจจุบันของเอเจนต์กับเพดานที่มีผลบังคับใช้ของมัน (นโยบายงบประมาณที่เชื่อมไว้ หากมี มิฉะนั้นคือตัวเลขรายเดือนของเอเจนต์เอง; ศูนย์หมายถึงไม่จำกัด) การข้ามเกณฑ์อ่อนจะบันทึกอุบัติการณ์ระดับคำเตือน; การใช้งบประมาณจนหมดจะบันทึกอุบัติการณ์ระดับรุนแรง และพลิกสถานะวงจรชีวิตของเอเจนต์เป็น paused บนผังองค์กร การสั่งงานยังคงรับงานจากเอเจนต์นั้นอยู่ ทริกเกอร์ไปป์ไลน์ BudgetThresholdCrossed สามารถเริ่มงานติดตามผลจากเหตุการณ์เดียวกันได้

บันทึกการรันทุกอันสามารถพกสายการลองใหม่ได้ — run id ของตัวแม่, ตัวนับจำนวนครั้งที่ลอง และสรุปการทำงานต่อ แผนภูมิต้นไม้การรันของการสนทนาจะแสดงหมายเลขครั้งที่ลองเมื่อมันไม่ใช่ศูนย์ การกด “ลองใหม่” จากแชทถือเป็นการสั่งงานใหม่ที่มีบันทึกการรันใหม่