Autonomous Agent Loop
Loop ที่รันà¸à¸±à¸•โนมัติ 24/7 — ดึงงานจาà¸à¸„ิว, spawn worker, เช็คสถานะ, retry เมื่ภfail, dead-letter เมื่à¸à¹€à¸à¸´à¸™ max retry
Agent Loop à¸à¸¢à¸¹à¹ˆà¹ƒà¸™ src/services/autonomous/agentLoop.ts — เป็น consumer ตัวเดียวที่à¸à¹ˆà¸²à¸™ task queue à¹à¸¥à¸°à¸šà¸£à¸´à¸«à¸²à¸£ lifecycle ขà¸à¸‡ worker process ทั้งหมด
เทคนิคà¹à¸¥à¸°à¸«à¸¥à¸±à¸à¸à¸²à¸£
ทำไมต้à¸à¸‡à¹€à¸›à¹‡à¸™ Loop à¹à¸—นที่จะเป็น Message Queue?
ระบบ message queue à¹à¸šà¸š RabbitMQ/Kafka มี overhead ในà¸à¸²à¸£ setup à¹à¸¥à¸° maintenance Agent Loop ใช้ไฟล์ JSON เป็น persistent queue — ทำงานได้โดยไม่ต้à¸à¸‡à¸¡à¸µ external dependency:
- Zero external deps — ใช้
fs.watchตรวจจับà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸‚à¸à¸‡à¹„ฟล์ queue - File-based atomicity — à¸à¹ˆà¸²à¸™/เขียนไฟล์เดียวภายใต้ lock ทำให้หลาย process à¹à¸‚่งà¸à¸±à¸™ consume ได้
- Self-debouncing —
ourWriteInProgressflag ป้à¸à¸‡à¸à¸±à¸™ self-trigger เมื่à¸à¹€à¸£à¸²à¹€à¸›à¹‡à¸™à¸„นเขียนเà¸à¸‡ - Cross-platform — ไฟล์ JSON ทำงานได้ทุภOS ไม่ต้à¸à¸‡à¸•ิดตั้ง message broker
Lease-Based Concurrency — ป้à¸à¸‡à¸à¸±à¸™ Task Duplicate
ปัà¸à¸«à¸²à¸„ลาสสิà¸à¸‚à¸à¸‡ distributed workers: มี 2 worker เห็น task เดียวà¸à¸±à¸™à¹à¸¥à¸°à¸—ำงานซ้ำ Lease à¹à¸à¹‰à¸›à¸±à¸à¸«à¸²à¸™à¸µà¹‰à¹‚ดยไม่ต้à¸à¸‡à¹ƒà¸Šà¹‰ distributed lock:
Main Loop Queue File (.json) Worker Process
│ │ │
│ getNextTask() │ │
│─────────────────────────────►│ │
│◄─── task {id, status:pending}│ │
│ │ │
│ leaseTask(id, agentId) │ │
│─────────────────────────────►│ │
│ (atomic: check no lease │ │
│ → write leaseOwner + │ │
│ leaseExpiresAt) │ │
│◄─────── true (leased) ──────│ │
│ │ │
│ spawnWorker(prompt) │ │
│─────────────────────────────────────────────────────────────►│
│◄──── WorkerSession {id, pid} │ │
│ │ │
│ â•â•â• LOOP â•â•╠│ │
│ while running: │ │
│ checkWorker(sessionId) │ │
│ ────────────────────────────────────────────────────────►│
│ ◄─── "running" / "completed" / "failed" │
│ │ │
│ │ (worker ทำงาน autonomously) │
│ │ │
│ [completed] → releaseLease │ │
│ → markCompleted │ │
│ │ │
│ [failed] → markFailed │ │
│ → retryTask() │ │
│ → stopWorker() │ │
│ │ │
│ [timeout 30m] → stopWorker │ │
│ → releaseLease│ │
│ → retryTask() │ │
- Lease owner — ระบุว่า worker ไหนà¸à¸³à¸¥à¸±à¸‡à¸—ำ task นี้
- Lease expiry — ถ้า worker crash โดยไม่ release, lease จะหมดà¸à¸²à¸¢à¸¸à¹€à¸à¸‡ → worker à¸à¸·à¹ˆà¸™à¸£à¸±à¸šà¸‡à¸²à¸™à¸•่à¸à¹„ด้
- Startup recovery — ตà¸à¸™
startLoopจะรภ2 วินาที (ให้ process เà¸à¹ˆà¸²à¸•ายà¹à¸™à¹ˆà¹†) à¹à¸¥à¹‰à¸§à¹€à¸£à¸µà¸¢à¸expireLeases()ล้าง stale lease ทั้งหมด
Retry with Exponential Backoff
Task ที่ fail ไม่ได้à¹à¸›à¸¥à¸§à¹ˆà¸²à¸•้à¸à¸‡ dead-letter ทันที:
Retry attempt Backoff delay Cumulative wait
───────────── ───────────── ──────────────
1 base × 2¹ = 30s 30s
2 base × 2² = 60s 90s
3 base × 2³ = 120s 210s
4 base × 2ⴠ= 240s 450s
5 (max) base × 2ⵠ= 480s 930s (~15 min)
After max retries → dead_letter queue
Dead-letter preserves: title, description, lastError, errorLog, retryCount
- Exponential backoff — base = 15s, factor = 2 — ลดà¸à¸²à¸£à¸–ล่ม queue เมื่ภtask fail ซ้ำๆ
- Max retries — default 5 ครั้งต่ภtask
- Retry interval —
retryAftertimestamp ป้à¸à¸‡à¸à¸±à¸™à¹„ม่ให้ retry à¸à¹ˆà¸à¸™à¹€à¸§à¸¥à¸²à¸—ี่à¸à¸³à¸«à¸™à¸” - Dead-letter queue — task ที่ใช้ retry หมดจะถูà¸à¸¢à¹‰à¸²à¸¢à¹„ปสถานะ
dead_letterพร้à¸à¸¡deadLetterReasonà¹à¸¥à¸°errorLog— ไม่หายไปไหน ตรวจสà¸à¸šà¸¢à¹‰à¸à¸™à¸«à¸¥à¸±à¸‡à¹„ด้
Worker Lifecycle + Concurrent Cap
Main loop มีà¸à¸¥à¹„à¸à¸ˆà¸³à¸à¸±à¸”จำนวน worker พร้à¸à¸¡à¸à¸±à¸™:
- MAX_CONCURRENT_WORKERS = 3 — ป้à¸à¸‡à¸à¸±à¸™à¹„ม่ให้ spawn worker มาà¸à¹€à¸à¸´à¸™à¹„ปจนเครื่à¸à¸‡à¸žà¸±à¸‡
- Loop poll interval — ถ้า worker เต็ม → sleep
LOOP_SLEEP_MS(5s) à¹à¸¥à¹‰à¸§à¸•รวจใหม่ - Worker timeout = 30 นาที — task ที่รันนานเà¸à¸´à¸™à¸ˆà¸°à¸–ูภkill เพื่à¸à¸„ืน resource
- Worker poll = 10s — เช็คสถานะ worker ทุภ10 วินาทีผ่าน supervisor IPC
Supervisor Integration — Process Health
Worker ไม่ได้ถูà¸à¸£à¸±à¸™à¹‚ดยตรง à¹à¸•่ spawn ผ่านSupervisor process (child_process):
Agent Loop Supervisor Worker
│ │ │
│ sendRequest({type:'spawn'}) │ │
│─────────────────────────────►│ │
│ │ spawn child_process │
│ │────────────────────────►│
│◄─── {ok:true, sessionId, pid}│ │
│ │ │
│ sendRequest({type:'attach'}) │ │
│─────────────────────────────►│ │
│ │ check process status │
│◄─── {status, isRunning} │ │
│ │ │
│ sendRequest({type:'stop'}) │ │
│─────────────────────────────►│ │
│ │ kill + cleanup │
│ │────────────────────────►│
- Crash isolation — worker crash ไม่ทำให้ loop crash — supervisor à¹à¸¢à¸ process คนละตัว
- Output capture — supervisor เà¸à¹‡à¸š stdout/stderr ขà¸à¸‡ worker ไว้ใน
~/.claude/daemon/jobs/{sessionId}/output.log - Health monitoring — loop เช็คผ่าน supervisor à¹à¸—นที่จะ monitor PID โดยตรง (PID reuse problem)
Integration Points — Peer + Cron + Watch
Agent Loop ไม่ได้à¸à¸¢à¸¹à¹ˆà¹€à¸”ี่ยวๆ — มัน integrate à¸à¸±à¸šà¸£à¸°à¸šà¸šà¸à¸·à¹ˆà¸™à¸‚à¸à¸‡ Clew:
- Peer todo listener — ฟัง
/peer-todoHTTP endpoint → รับ task จาภremote peer → add เข้า queue - Cron scheduler —
daemonCronSchedulerfire task ตาม schedule → add เข้า queue - File watcher —
fs.watchบน queue file → ตรวจจับ task ใหม่จาภprocess à¸à¸·à¹ˆà¸™ (เช่นจาภCLI/task add)
architecture à¹à¸šà¸šà¸™à¸µà¹‰à¸—ำให้ task เข้า queue ได้จาà¸à¸«à¸¥à¸²à¸¢à¸Šà¹ˆà¸à¸‡à¸—าง — CLI, remote peer, cron — à¹à¸•่มี consumer เดียว (loop) ที่คุมà¸à¸²à¸£ execute
Task Log Persistence
ทุภtask มี log ขà¸à¸‡à¸•ัวเà¸à¸‡:
- Per-task log —
~/.claude/daemon/logs/{taskId}.log— 500 บรรทัดสุดท้ายขà¸à¸‡ worker output - Error extraction — ตà¸à¸™ task fail, loop จะ extract non-noise lines (20 บรรทัดสุดท้าย) เà¸à¹‡à¸šà¹€à¸›à¹‡à¸™
errorLog[]ใน queue entry - Worker exit code — บันทึà¸à¸§à¹ˆà¸² worker จบด้วย exit code 0 (success) หรืภ1 (failure)
Crash Recovery Flow
ถ้า loop process ตาย (kill -9, power loss, OOM):
Previous Run Crash
│
â–¼
startLoop() called
│
├── loadQueue() — à¸à¹ˆà¸²à¸™à¹„ฟล์ queue จาภdisk
│
├── sleep(2000ms) — รà¸à¹ƒà¸«à¹‰à¹à¸™à¹ˆà¹ƒà¸ˆà¸§à¹ˆà¸² process เà¸à¹ˆà¸²à¸•ายà¹à¸¥à¹‰à¸§
│
├── expireLeases() — ล้าง lease ทั้งหมดที่หมดà¸à¸²à¸¢à¸¸
│ (task ที่ถูภlease โดย processID เà¸à¹ˆà¸²à¸ˆà¸°à¸–ูภreset → pending)
│
├── start heartbeat (ทุภ60s)
│
├── start cron scheduler
│
├── start peer sharing
│
├── start file watcher
│
└── MAIN LOOP ──► getNextTask() → processTask() → loop
Task Lifecycle (State Machine)
┌──────────â”
│ pending │◄──────────────────────────────â”
└────┬─────┘ │
│ leaseTask() │
▼ │
┌──────────┠│
│in_progress│ │
└────┬─────┘ │
┌───────────┼───────────┠│
▼ ▼ ▼ │
┌──────────┠┌──────────┠┌──────────┠│
│completed │ │ failed │ │cancelled │ │
└──────────┘ └────┬─────┘ └──────────┘ │
│ retryTask() │
├── retryCount < max → backoff → ────┘
│
└── retryCount ≥ max
│
â–¼
┌──────────────â”
│ dead_letter │
│ (preserved │
│ for review) │
└──────────────┘
ไฟล์ที่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้à¸à¸‡
| ไฟล์ | หน้าที่ |
|---|---|
src/services/autonomous/agentLoop.ts | Main loop — start, stop, processTask, worker lifecycle |
src/services/autonomous/taskQueue.ts | Queue CRUD, lease management, retry, dead-letter, file watcher |
src/services/autonomous/daemonMode.ts | Daemon entry point — calls startLoop/stopLoop |
src/Task.ts | Task type definitions, state machine, task ID generation |
src/tasks/LocalAgentTask/ | Local worker task — UI + lifecycle |
src/tasks/RemoteAgentTask/ | Remote worker task — UI + lifecycle |
src/components/AutonomousExecutionAccordion.tsx | UI component สำหรับà¹à¸ªà¸”ง task queue ใน REPL |