Khám phá Learn Học nhanh Stream About Jokes
SHIP WITH CLAUDE Build App nội bộ cho công ty. Ship app của riêng bạn với Claude Code Đăng ký →
Bài viết

Agent tự build qua đêm được rồi, thứ còn thiếu là người canh ca

Orchestrator biết chia việc cho subagent, nhưng ai canh chính nó? Ghi chép về một lớp supervisor đứng ngoài: canh, kiểm chứng, giữ máy sống, quyết việc vặt, và tự hỏi ROI có dương thật không.

Agent tự build qua đêm được rồi, thứ còn thiếu là người canh ca

Khi agent đã biết làm việc, câu hỏi kế tiếp không phải là giao thêm việc, mà là ai đang nhìn cả ca trực đó.

Tóm tắt cho người đọc nhanh

Câu hỏi Kết quả từ lần chạy thật
Agent có tự build nhiều phase qua đêm được không? Được, nếu có một lớp supervisor đứng ngoài canh ca
Lớp supervisor bắt được gì mà test xanh không thấy? Máy sắp sập (disk, swap, process rò rỉ), test chỉ xanh trong thư mục bẩn, rule chọn model bị template đánh lừa
Kiểm chứng độc lập có tìm ra bug trong bản build chính không? Không. Mọi lần chạy lại đều khớp. Nó là bảo hiểm, không phải máy tìm bug
Tool có đáng công build không? Chỉ khi chạy build dài đều đặn. Một người dùng mà 950 test là dấu hiệu build quá tay
Bước tiếp theo Đo ROI tự động mỗi lần chạy, tự tắt thứ không dùng, cắt phần đầu cơ

Mình muốn thử một chuyện khá đơn giản trên giấy: giao một bản build lớn, kiểu backend Go + vài SPA React theo hướng API-first, cho agent làm trong lúc mình ngủ. AgentKit đã lo được phần plan → cook trong một session, session đó đóng vai orchestrator, biết chia phase, giao việc cho subagent rồi gom lại. Nhưng mình cố tình đặt một luật: orchestrator chỉ điều phối, không tự viết code, vì nếu nó vừa quản lý vừa nhảy vào sửa thì rất khó biết lúc nào kế hoạch đang trật.

Nghe tới đó thì mọi thứ có vẻ đã xong. Có plan, có subagent, có test, có commit, vậy chỉ cần chạy là được. Nhưng mình nhận ra còn một khoảng trống hơi khó chịu: ai canh cái session đang canh cả đống session kia?

Một lần mình để một Claude session riêng làm “driver” bằng tay. Nó không build sản phẩm, không được quyền tự quyết chuyện business, chỉ đứng ngoài quan sát orchestrator, gửi lệnh khi cần, kiểm chứng kết quả và giữ cái máy không tự chết giữa chừng. Sau ca đó, mình đóng gói vai trò này thành một tool chung, cũng gọi là driver.

Ba lớp: driver đứng ngoài canh ca, orchestrator chỉ lập plan và giao việc, subagent viết code

Hình 1. Ba lớp và ranh giới của từng lớp.

Bảng 1. Ai làm gì, ai không được làm gì

Lớp Làm Không được làm
Driver Canh theo sự kiện, kiểm chứng trên worktree sạch, giữ máy, quyết việc vặt có log, báo cáo Viết code sản phẩm, tự ký cổng cần người, đụng dữ liệu của dự án khác
Orchestrator Lập plan, giao việc có ranh giới file rõ, review diff, chạy lại test, merge Tự viết code, merge phase làm dở
Subagent Viết code trong file được giao, có test, commit vào nhánh riêng Tự sinh thêm agent con, sửa file ngoài phạm vi

Càng làm, mình càng thấy đây không phải thêm một agent cho vui. Nó là một lớp supervisor: biết lúc nào nên im, lúc nào phải dừng, lúc nào lời “xong rồi” cần được tin, và lúc nào cái máy đang sắp biến cả buổi build thành một đống process mồ côi.


Agent làm được nhiều rồi, nhưng một session không tự nhìn thấy lưng mình

Ban đầu mình chạy orchestrator kiểu headless bằng claude -p. Cách đó tiện nếu chỉ muốn ném prompt vào rồi lấy output, nhưng rất tệ khi công việc kéo dài nhiều phase. Mình không nhìn được session đang đứng ở đâu, không biết nó đang hỏi một câu cần người trả lời hay chỉ đang in log dài, và cũng khó gửi một chỉ dẫn sửa hướng giữa chừng.

Sau đó mình chuyển orchestrator vào tmux. Driver nhìn được màn hình, theo dõi trạng thái, và có thể gửi lệnh tiếp vào đúng session đang chạy. Một bài học khá ngớ ngẩn nhưng mất thời gian mới thấy: tin nhắn dài gửi qua tmux có thể bị cắt mất nửa đầu. Nếu lệnh sửa kế hoạch dài mà mất đoạn mở đầu, agent vẫn nhận được vài ý cuối rồi tự suy diễn phần còn lại, mà đó là kiểu lỗi rất khó phát hiện.

Nên driver không gửi “bài văn” qua terminal nữa. Nó ghi nội dung vào file, rồi chỉ gửi một dòng bảo orchestrator đọc file đó. Nghe nhỏ, nhưng từ lúc đó những amendment dài bớt bị biến thành trò điện thoại hỏng.

Điều quan trọng hơn là watcher không chạy theo kiểu ngủ 5 phút rồi mở mắt nhìn một lần. Nó theo sự kiện: phase vừa xong, session chuyển idle, màn hình có câu hỏi, process chết, disk sắp đầy, memory pressure tăng, load bất thường. Vì một ca build không hỏng theo nhịp năm phút; có lúc nó treo trong 20 giây, có lúc nó chạy bình thường ba tiếng rồi một subagent tự fan-out làm máy gần ngất.

Driver không cần dùng model để nhìn những chuyện đó. Script đọc trạng thái process, dung lượng disk, log phase, tmux pane và kết quả command được. Phần canh, kiểm và đọc trạng thái này tốn 0 token, thay vì hàng trăm lần gọi một model chỉ để nó chụp màn hình rồi diễn giải lại “hình như vẫn đang chạy”.


Lời “xong rồi” chưa phải là bằng chứng

Có một điều mình khá cảnh giác khi để agent build dài: orchestrator báo test xanh không có nghĩa là phần nó vừa làm thật sự đứng vững. Nó có thể chạy test trong working directory đang bẩn, dựa vào file chưa commit, cache cũ, fixture nằm ngoài version control, hoặc vô tình được một process khác làm hộ.

Vậy nên mỗi lần orchestrator báo xong một phase đáng kể, driver checkout commit đó vào một worktree sạch rồi chạy lại test. Đoạn này không có gì thông minh lắm, nhưng nó độc lập. Môi trường sạch không biết lịch sử session trước đó, nên nếu test chỉ xanh nhờ rác còn sót lại thì nó lộ ra khá nhanh.

Mình còn thử một lớp khó chịu hơn là mutation probe. Không chỉ chạy test rồi thấy xanh, driver cố tình cài một lỗi nhỏ có chủ đích vào vùng đang được test, rồi chờ gate bắt lỗi đó. Nếu test vẫn xanh thì vấn đề không phải “test chạy được”, mà là test chưa chứng minh được điều mình tưởng nó đang chứng minh.

Một lời báo xong đi qua worktree sạch, chạy lại test và mutation probe trước khi merge

Hình 2. Vòng kiểm chứng: không đạt ở bước nào thì trả về, không có chuyện chạy lại cho tới khi xanh.

Có lần test ở thư mục làm việc xanh hoàn toàn, nhưng checkout sạch thì đỏ. Hóa ra một fixture cần thiết có đuôi .log, mà .gitignore có *.log, nên file đó chưa từng được commit. Trong session đang chạy thì fixture vẫn nằm đó nên ai cũng tưởng ổn. Sang worktree sạch, sự thật hiện ra ngay.

Lần khác, một test fail rồi chạy lại pass. Orchestrator muốn ghi nhận là flaky và đi tiếp, nhưng driver từ chối. “Pass ở lần chạy lại” không phải là sửa. Cuối cùng phải tìm nguyên nhân gốc, sửa nó, rồi chứng minh bằng năm lần xanh liên tiếp. Cách này chậm hơn một chút, nhưng nếu mình đang định bỏ máy vài tiếng thì thà chậm ở đây còn hơn sáng dậy có một commit nhìn rất sạch mà không tái lập được.

Điểm thú vị là lớp kiểm chứng độc lập này, tính trên bản build chính, không bắt được bug nào. Mọi lần chạy lại cuối cùng đều khớp với orchestrator. Nói thẳng ra, nó là bảo hiểm chứ không phải máy đẻ bug. Mình vẫn nghĩ nó đáng có ở những chỗ rủi ro, nhưng không nên kể nó như một cỗ máy luôn tìm ra lỗi mà mọi người bỏ sót.


Cái dễ chết trước không phải plan, mà là cái máy

Có lúc driver báo disk chỉ còn khoảng 500 MB. Lý do đầu tiên là cache build phình lên, chuyện này còn dễ xử vì cache tái tạo được. Nhưng truy tiếp ra thì có một file dump 37 GB của một dự án khác nằm trên máy. Driver chỉ dọn cache của chính nó, chừa nguyên dữ liệu không thuộc phạm vi mình giao. Nó không có quyền thấy disk đầy là quét đại, vì “dọn máy” mà xóa mất thứ người khác đang cần thì còn tệ hơn build fail.

Một sự cố khác làm mình đổi luật fan-out. Orchestrator giao việc cho subagent, rồi subagent lại tự sinh thêm agent con, tổng cộng sáu agent chạy cùng lúc. Lúc nhìn lại, swap gần đầy, load average chạm khoảng 250 trên máy 10 core. Giới hạn ban đầu là tối đa hai agent làm việc cùng lúc, nhưng nó chỉ đếm tầng trên cùng. Từ đó luật đổi thành đếm cả agent lồng nhau. Một cây process nhìn từ trên xuống có thể có vẻ hợp lý, nhưng CPU không quan tâm ai là con ai.

Có lần nữa, máy bị tám process yes mồ côi từ một session khác chiếm gần năm core suốt khoảng 15 tiếng, tất cả chỉ đang ghi vào /dev/null. Tắt chúng xong, load rớt từ khoảng 185 xuống khoảng 60. Con số vẫn chưa đẹp, nhưng ít nhất mình biết đâu là phần do build, đâu là phần do rác từ một ca cũ.

Lúc đó mình mới thấy supervisor không thể chỉ biết nhìn trạng thái app. Nó phải lần được tới process nào đang ăn tài nguyên, process đó thuộc session nào, có thể dừng an toàn không, và thứ gì không được đụng vào. Một hệ thống tự build qua đêm mà không có hygiene cho máy thì nghe hiện đại, nhưng thật ra chỉ đang xếp thêm việc lên một bàn làm việc đầy đồ cũ.


Watcher cũng có thể hoảng nhầm như con người

Lúc đầu, driver báo động sai khá nhiều. Có lúc trên terminal có đoạn text chứa “Enter to confirm”, watcher tưởng orchestrator đang hỏi người dùng và dừng cả flow. Thật ra đó chỉ là một đoạn ghi chú đang hiện trên màn hình. Có lúc watcher tưởng máy hết swap vì macOS đang co giãn swap động, nhưng memory pressure vẫn chưa phải mức nguy hiểm.

Một lỗi khác là driver đếm phase bằng cách đọc bảng kế hoạch, rồi tính luôn những dòng con trong bảng là phase độc lập. Báo cáo tiến độ vì vậy sai, ETA cũng sai. Khi một tool bắt đầu báo “72%” cho mình, con số đó cần có định nghĩa nghiêm túc, chớ không phải đếm được bao nhiêu dòng markdown.

Mình sửa dần bằng cách để watcher phân biệt tín hiệu cứng với text tình cờ xuất hiện. Câu hỏi thật phải có trạng thái chờ input rõ ràng, không phải chỉ có một keyword. Cảnh báo bộ nhớ phải dựa vào memory pressure và xu hướng process, không phải một con số swap. Tiến độ phải đọc từ phase cấp cao có trạng thái rõ ràng, không lấy mọi hàng bảng.

Một lớp khác cũng bị lộ sai là model selector. Nó thấy file phase nào cũng có mục “Security Considerations” theo template, thế là gắn nhãn security cho mọi phase và không bao giờ hạ xuống model rẻ hơn. Đây là kiểu bug khá hài: một rule an toàn bị template đánh lừa, nên tối ưu chi phí thành vô dụng.

Mình chuyển sang phân loại theo việc thật phải làm, không theo từ xuất hiện trong form. Những việc nhạy cảm như tiền, auth, bảo mật vẫn luôn dùng model mạnh nhất và driver không được tự hạ. Còn việc đọc log, check trạng thái, chạy test, kiểm fixture thì script làm. Việc ít rủi ro hơn mới có chỗ để chọn model vừa đủ.

Bảng 2. Các sự cố thật trong lần chạy và thứ chúng để lại

Sự cố Triệu chứng Nguyên nhân gốc Xử lý Quy tắc mới
Disk gần đầy còn ~500 MB cache build phình + file dump 37 GB của dự án khác dọn cache tái tạo được của mình chỉ dọn thứ trong allowlist, không đụng dữ liệu người khác
Fan-out lồng nhau 6 agent, swap gần đầy, load ~250 / 10 core agent con tự sinh agent chặn dispatch mới giới hạn đếm cả agent lồng nhau
Process mồ côi ~5 core bị đốt suốt ~15 giờ 8 process yes rò từ một session khác, ghi vào /dev/null tắt sau khi xác nhận không có state supervisor phải truy được process nào thuộc session nào
Báo động giả tưởng có câu hỏi, tưởng hết swap khớp keyword trên màn hình; macOS co giãn swap động đổi tín hiệu câu hỏi chỉ tính ở vùng prompt; dùng memory pressure thay vì swap
Đếm sai tiến độ % và ETA lệch đếm cả dòng trong bảng con đếm phase cấp cao tiến độ phải có định nghĩa
Test chỉ xanh khi thư mục bẩn đỏ trên checkout sạch .gitignore có *.log, fixture chưa commit commit fixture test canh mọi fixture phải được git theo dõi
Model selector sai mọi phase bị gắn security template có mục “Security Considerations” phân loại theo phạm vi việc việc tiền/auth/bảo mật luôn dùng model mạnh nhất, phần còn lại chọn vừa đủ
Test flaky đỏ rồi xanh khi chạy lại chờ container không có giới hạn sửa gốc, 5 lần xanh liên tiếp “pass ở lần chạy lại” không được tính
Tin nhắn bị cắt orchestrator nhận nửa sau gửi đoạn dài qua tmux ghi file, gửi một dòng trỏ tới file không gửi văn bản dài qua terminal

Người lái không được tự đổi hướng kinh doanh

Có những việc agent có thể quyết khi mình vắng mặt, nhưng có những việc không nên. Nếu một quyết định business chưa rõ, driver không được bịa ra “đáp án tốt nhất” rồi để nó thành code hard-code. Nó chuyển điểm mơ hồ đó thành config có giá trị mặc định hợp lý, đặt vào một preset có tên, ghi log quyết định, rồi đi tiếp.

Cách này giữ flow không bị đứng chỉ vì một câu hỏi nhỏ, nhưng vẫn để người thật quay lại biết chính xác cái gì đã được giả định. Khác biệt nằm ở chỗ default đó có thể đổi được, còn một suy đoán giấu trong logic thì vài tuần sau thường không ai nhớ tại sao nó ở đó.

Driver cũng không tự ký các cổng cần người chịu trách nhiệm. Mình có thử adapter cho một kit khác có approval gate do người ký. Driver có thể chuẩn bị bằng chứng, nhắc đúng lúc, báo block, nhưng không được giả làm người phê duyệt. Đây là một ranh giới mình muốn giữ cứng, vì automation rất dễ trượt từ “giảm việc vặt” sang “tự thay người quyết định”.

Báo cáo cũng phải đủ ngắn để đọc từ xa. Mỗi giai đoạn driver gửi ba khối DONE, NEXT, HEADS-UP, kèm phần trăm và ETA. DONE nói thứ đã qua gate nào, NEXT nói phase kế tiếp thực sự là gì, còn HEADS-UP nói những giả định, rủi ro tài nguyên hoặc câu hỏi cần người trả lời. Không cần một bản tường thuật toàn bộ terminal; mình chỉ cần biết mình có nên thức dậy hay không.

Nếu giữa chừng đổi hướng, driver dùng một file amendment. Chẳng hạn ban đầu plan đi theo module kỹ thuật, rồi mình muốn chuyển sang luồng user-centric: job → flow → contract → UI, và thêm Storybook để chốt component. Amendment không phá phase đang chạy dở. Nó ghi rõ điều gì giữ nguyên, điều gì áp dụng từ phase nào, và orchestrator đọc nó trước khi lập phần plan kế tiếp.


Đóng gói xong rồi, mình bắt đầu nghi ngờ chính thứ mình vừa làm

Tool driver hiện là một CLI bằng bash + Python, chạy được với AgentKit và có adapter thử nghiệm cho một kit khác. Điều hơi tréo ngoe là chính nó được build bằng đúng quy trình mà nó giám sát: khoảng 19 phase, gần 950 test. Chạy full test mất khoảng 17 phút, mà khi mình nhìn con số đó, cảm giác đầu tiên không phải tự hào lắm mà là hơi lo.

950 test cho một tool một người dùng có thể là bảo hiểm hợp lý, cũng có thể là build quá tay. Adapter kia chưa có ai dùng thật. Lớp tự học nghe rất đẹp, nhưng chưa có dữ liệu thì nó chỉ là hạ tầng chờ dữ liệu. Theo thiết kế, driver lưu trace mỗi lần chạy, có bộ bắt pattern sự cố được seed từ những chuyện như disk đầy, nested agent, fixture bẩn và watcher báo nhầm. Nhưng một loại sự cố phải gặp ít nhất hai lần mới thành “bài học”, nên lần chạy đầu tạo ra 0 bài học. Đó là đúng, nhưng nó cũng nhắc mình rằng không được lấy chữ “self-learning” làm thành tích khi nó chưa học gì.

Vòng tự học: trace, bắt pattern, bài học, retro đo ROI, đề xuất và build-to-delete

Hình 3. Vòng tự học. Mọi thay đổi lên chính tool đều qua đề xuất có người duyệt.

Bảng 3. Vài con số của chính cái tool

Chỉ số Giá trị
Số phase để build tool khoảng 19
Số test tự động gần 950
Thời gian một lần chạy full test trên worktree sạch khoảng 17 phút
Phần canh, kiểm, đọc trạng thái script, 0 token
Bug kiểm chứng độc lập tìm thấy trong bản build chính 0
Bug kiểm chứng trực tiếp trên máy tìm thấy trong chính tool vài cái (cache đang dùng suýt bị dọn, fixture chưa commit, model selector sai, báo cáo dồn cục)
Bài học tạo ra sau lần chạy đầu 0, theo thiết kế

Mình vẫn giữ retro sau mỗi lần chạy, vì nó giúp phân biệt phần nào tool thật sự tự xử lý được với phần nào chỉ tạo thêm log. Nhưng mình không muốn tool này tiến hóa thành một harness để xây harness. Nguyên tắc ban đầu là làm tay trước, thấy việc đó lặp thật, rồi mới đóng gói. Nếu chính driver bắt đầu đẻ thêm engine chỉ vì “biết đâu sau này cần”, thì nó đang phản lại nguyên tắc của nó.

Nút cổ chai thật cũng không phải lúc nào là người giám sát. Nhiều khi nó là máy: container của dự án khác, app nặng đang mở, process rò rỉ, ổ đĩa bị chiếm bởi dump cũ. Driver có thể phát hiện và giảm thiệt hại, nhưng vẫn cần một người giám sát cái giám sát đó. Khi policy sai, watcher có thể chăm chỉ làm sai rất lâu.

Bảng 4. Ưu và nhược, kèm bằng chứng

Điểm Bằng chứng
Ưu Bắt được thứ test xanh không thấy cache đang dùng suýt bị dọn; test chỉ xanh trong thư mục bẩn; model selector sai
Ưu Rẻ ở phần canh script lo watch, verify, đọc state; model chỉ thức khi có sự kiện
Ưu Giữ máy sống disk, swap, process rò rỉ được phát hiện và xử lý trước khi build sập
Ưu Lái hướng giữa chừng amendment bằng file, không phá phase đang chạy
Nhược Kiểm chứng độc lập là bảo hiểm 0 bug tìm thấy trên bản build chính
Nhược Build quá tay cho một người dùng 19 phase, gần 950 test, 17 phút mỗi lần kiểm
Nhược Nhiều phần còn đầu cơ adapter chưa ai dùng, lớp tự học chưa có dữ liệu
Nhược Nút cổ chai thật là cái máy container dự án khác, app nặng, process rò rỉ
Nhược Vẫn cần người giám sát cái giám sát nhiều lỗi của tool chỉ lộ khi chạy trực tiếp trên máy

ROI chỉ dương khi nó thật sự có ca trực

Phần tiếp theo mình muốn làm không phải thêm “thông minh” cho driver. Mình muốn cắt bớt phần đầu cơ. Một engine tự áp patch chẳng hạn, mình bỏ; driver chỉ ghi đề xuất ra markdown để người xem. Tự sửa hạ tầng giám sát nghe hấp dẫn, nhưng quyền ghi đè ở lớp ngoài cùng là thứ rất dễ tạo sự cố khó lần.

Mỗi lần chạy, tool cũng cần đo ROI tự động thay vì mình tự kể cảm giác. Mình muốn biết có bao nhiêu lần nó tự xử lý mà không cần người, có bug thật nào bị chặn trước khi merge, có sự cố tài nguyên nào được xử lý, người vẫn phải nhảy vào sửa bao nhiêu lần, và đổi lại tốn bao nhiêu model wake, token với phút verify.

Bảng 5. ROI đo thế nào, tự động mỗi lần chạy

Phía Chỉ số Lấy từ đâu
Giá trị số lần tự xử lý không cần người (trả lời câu hỏi, chờ hết limit, gỡ pause tài nguyên) trace của lần chạy
Giá trị bug thật bị chặn trước khi merge kết quả verify FAIL dẫn tới sửa
Giá trị sự cố tài nguyên đã xử lý sự kiện disk, memory, load, process lạ
Chi phí số lần người phải sửa tool ghi nhận can thiệp
Chi phí model wake, token, thời gian, phút verify trace + log verify
Kết luận một dòng ROI value=… cost=… ratio=… mỗi lần chạy, cộng dồn qua các lần báo cáo cuối

Mình cũng cho nó build-to-delete chính nó. Feature nào ba lần chạy không kích hoạt thì tự tắt, nếu thêm năm lần nữa vẫn bằng 0 thì đề xuất xóa, trừ các chốt an toàn. Và trước khi có ba lần chạy thật, đóng băng feature mới. Nghe hơi ngược với tinh thần “cứ thêm đi cho đủ”, nhưng tool giám sát càng ít hành vi bí ẩn thì càng dễ tin.

Cuối cùng là vệ sinh máy trước khi giao việc qua đêm: kiểm disk, process lạ, container còn chạy, cache thuộc phạm vi nào, memory pressure nền. Một ca trực tốt không bắt đầu từ prompt hay; nó bắt đầu từ việc không đưa agent vào một cái máy đang có sẵn năm quả mìn.

Nếu mình không thật sự chạy build dài đều đặn, driver này chỉ là một món đồ chơi đẹp với gần 950 test. Còn nếu ca trực lặp đủ nhiều, phần nó mua lại không chỉ là thời gian ngủ, mà là khả năng để mình quay lại nhìn đúng những quyết định đáng nhìn. Bạn có việc nào đang muốn giao cho agent chạy lâu hơn một giờ, nhưng chưa dám vì chưa biết ai sẽ canh nó?

#ai #claude-code #agentkit #multi-agent #orchestration #automation

Bài viết liên quan

Vibe coding không phải là gõ đại — cái khó là gọi đúng tên vấn đề cho AI

idea

Cùng một tính năng, đổi cách mình gọi tên vấn đề thôi, AI có thể viết ra hai codebase khác hẳn nhau. Đây là lý do, và cẩm nang tra keyword theo triệu chứng.

Tự động hoá hậu kỳ cho screen recording — tới đâu là hết

idea

Dựng một screen recording tốn thời gian nhất ở khâu ít cần suy nghĩ nhất: cắt khoảng lặng, tua đoạn ngồi chờ, thêm zoom. Để máy làm hết phần đó, mình còn nguyên sức cho phần thật sự quyết định video hay hay dở.

—
0:00

Chia sẻ ảnh

Bắt đầu gõ để tìm kiếm...