Điều kiện dừng: để “xong” có nghĩa
TL;DR
- Một vòng lặp chỉ đáng tin bằng đúng điều kiện dừng của nó. “Xong” phải là một điều kiện mà một mô hình khác kiểm tra được (bài kiểm tra đạt, lint sạch, lược đồ (schema) hợp lệ), không bao giờ là lời tự nhận của bên làm việc.
- Leo qua bốn nấc: một vòng
while ! npm testtrần kèm giới hạn, rồi Stop hook (exit 2, 8 lần chặn liên tiếp tự ghi đè), rồi/goal(một mô hình riêng chấm điều kiện ở mỗi lượt), rồi chạy goal theo lịch.- Tách bộ kiểm chứng thành một agent riêng, viết điều kiện sao cho kiểm chứng được, bác bỏ được và rẻ để đánh giá, rồi giới hạn mọi vòng lặp bằng ba chốt dừng cứng. Lần chạy thực hành bên dưới là thật.
Một vòng lặp tự chấm việc của chính nó sẽ báo “xong” trước khi nó thật sự xong. Đó là toàn bộ vấn đề phần này giải quyết. Ở Phần 2 bạn đã gọi tên khối bộ kiểm chứng và chạy một vòng lặp tối giản; ở đây bạn làm cho điều kiện dừng của nó đáng tin. Một điều kiện dừng kiểm chứng được (verifiable stop condition) là khác biệt giữa một vòng lặp bạn để chạy yên tâm và một vòng lặp phải ngồi canh. Phần này nối thẳng từ việc kiểm chứng trong một phiên ở vòng lặp tự kiểm chứng; giờ ta tổng quát hoá nó qua nhiều lần chạy.
Định nghĩa chuẩn của loạt bài, giữ nguyên tiếng Anh làm mỏ neo cho cả 9 phần, cũng chính là chỗ cụm “verifiable stop condition” lần đầu xuất hiện, và đây là phần mổ xẻ nó:
A loop is a recursive goal: you define a purpose and a verifiable stop condition, and the system iterates agents against it until the condition holds — without you prompting each turn.
Nói cách khác: bạn định nghĩa một mục đích và một điều kiện dừng kiểm chứng được, rồi để hệ thống lặp các agent cho tới khi điều kiện đó thành hiện thực, không cần bạn prompt ở từng lượt.
Yêu cầu trước: một harness đang hoạt động, một vòng lặp tối giản bạn chạy được, Claude Code phiên bản v2.1.139+ cho /goal, và một kho mã có lệnh kiểm tra (test) báo lỗi khi thất bại.
Vì sao tự chấm điểm thất bại?
Một bên tạo tự chấm sản phẩm của mình luôn lạc quan một cách có hệ thống, nên nó tuyên bố thắng lợi quá sớm. Chính bài viết của Anthropic về xây dựng agent hiệu quả gọi tên kịch bản thất bại này: thắng lợi sớm và những tính năng xong-giả, xảy ra đúng vì một chữ “xong” không kiểm chứng được thì chẳng có gì phản biện lại. Cách sửa gói trong một câu: “xong” phải được một mô hình khác với mô hình đã làm việc kiểm tra.
Đặt hai hình thái cạnh nhau. Một lời tự nhận nghe như “tôi thấy ổn, tính năng chạy được”. Một bài kiểm tra bên ngoài nghe như “bài kiểm tra trong test/auth thoát 0”. Cái thứ nhất là một cảm giác mà bên tạo tự nói về bài của chính mình. Cái thứ hai là một tạo phẩm mà bất kỳ ai, hay bất kỳ agent nào khác, đều chạy lại và xác nhận được. Chỉ cái thứ hai còn đứng vững khi bên tạo sai.
Chuyện này chia điều kiện dừng thành hai trường phái trung thực, và phần về bộ kiểm chứng sẽ quay lại với chúng: một điều kiện do một mô hình khác chấm, hoặc một tạo phẩm máy kiểm được như một lần chạy tích hợp liên tục (CI) xanh hay số lỗi về 0. Tốt nhất là kết hợp cả hai.
Điểm mấu chốt: Điều kiện dừng của một vòng lặp là một lời khẳng định về thực tế. Nếu chính agent viết ra đoạn mã là thứ duy nhất khẳng định điều đó, bạn đang có một lời tự nhận, không phải một sự kiểm chứng.
Bốn nấc của một điều kiện dừng là gì?
Điều kiện dừng có bốn nấc, mỗi nấc thêm một mức chặt chẽ mà nấc dưới còn thiếu. Bắt đầu từ dưới cùng và chỉ leo cao đúng mức công việc cần.
| Nấc | Cơ chế | Thêm gì so với nấc dưới |
|---|---|---|
| 1 | Vòng while ! npm test; do claude -p ...; done trần + giới hạn số vòng | một mã thoát máy kiểm được làm chốt chặn |
| 2 | Stop hook (exit 2 hoặc {decision:"block"}) | chặn hoàn tất cho tới khi bài kiểm tra đạt |
| 3 | /goal (một mô hình riêng chấm mỗi lượt) | một bộ chấm không phải là bên tạo |
| 4 | Chạy goal theo lịch (Routines) | điều kiện được kiểm lại tự động |
Nấc 1 là cả vòng lặp gói trong một dòng: while ! npm test; do claude -p "fix the failing test"; done. Lệnh kiểm tra chính là điều kiện, và mã thoát của nó là chốt chặn. Ngay cả ở đây cũng phải giới hạn (phần giới hạn để cuối bài); một vòng lặp nấc 1 không giới hạn là cách bạn thức dậy với một ví token cạn sạch.
Nấc 2 dời chốt chặn vào một Stop hook. Hook trả về exit 2 (hoặc JSON {decision:"block"}) để từ chối hoàn tất khi điều kiện chưa đạt, và Claude Code tự ghi đè sau 8 lần chặn liên tiếp để một vòng lặp kẹt không treo mãi. Giới hạn nằm sẵn trong cơ chế.
Nấc 3 là nơi bộ kiểm chứng thôi không còn là bên tạo. /goal (Claude Code v2.1.139+) để một mô hình riêng, nhỏ hơn, là Haiku, chấm điều kiện dừng ở mỗi lượt thay vì tin vào lời tự nhận của bên làm việc. Đây là nấc phần lớn vòng lặp nên nhắm tới: bạn phát biểu điều kiện bằng lời thường, và một mô hình khác quyết định ở mỗi lượt xem nó đã đạt chưa.
Nấc 4 bắn đúng điều kiện /goal đó theo một nhịp với Routines, để “xong” được kiểm lại trong lúc bạn đi vắng. Bộ máy nhịp thuộc Phần 6; ở đây chỉ cần biết nấc trên cùng là một bộ chấm chạy theo lịch.
Điểm mấu chốt: Các nấc là một thang leo về chuyện ai kiểm. Nấc 1 kiểm bằng một mã thoát, nấc 3 kiểm bằng một mô hình thứ hai, và khoảng cách giữa chúng đúng bằng sự lạc quan của bên tạo.
Viết một điều kiện dừng kiểm chứng được như thế nào?
Một điều kiện dừng tốt qua được ba bài kiểm: nó kiểm chứng được (có thứ gì đó xác nhận được), bác bỏ được (nó có thể thất bại), và rẻ để đánh giá (bạn chạy được nó ở mỗi lượt). Hãy diễn đạt “xong” sao cho nó sống sót qua cả ba, nếu không vòng lặp sẽ không.
| Điều kiện tồi | Vì sao trượt bài kiểm | Bản viết lại kiểm chứng được |
|---|---|---|
| ”tính năng đã xong” | không bác bỏ được, không có bài kiểm | ”mọi bài kiểm tra trong test/auth đạt VÀ lint sạch" |
| "đoạn mã trông ổn” | máy không kiểm chứng được | ”passes: true cho mọi câu chuyện trong prd.json" |
| "đã sửa xong lỗi” | không có ranh giới | ”bài kiểm tra hồi quy mới thoát 0 và không bài nào khác hỏng” |
Phản mẫu kinh điển là “cứ chạy ngày đêm cho tới khi tính năng xong”. Nó nghe như một mục tiêu nhưng hành xử như một cái bẫy, vì không có gì trong đó kiểm được, nên vòng lặp hoặc dừng theo cảm tính hoặc không bao giờ dừng. snarktank/ralph né chuyện này bằng cách chỉ dừng khi mọi câu chuyện trong PRD đọc ra passes: true, một tạo phẩm máy kiểm được chứ không phải một cảm giác.
Nước đi cần thấm: biến mọi chữ “xong” mơ hồ thành một lệnh thoát 0 hoặc khác 0, hoặc một con số phải chạm tới một mốc. Nếu bạn không viết được cái lệnh đó, bạn chưa có một điều kiện dừng.
Điểm mấu chốt: “Chạy cho tới khi tính năng xong” không phải một điều kiện dừng, đó là một điều ước. Một điều kiện dừng là một lệnh có mã thoát.
Vì sao bộ kiểm chứng nên là một agent riêng?
Một bộ kiểm chứng có chỉ dẫn khác, và tuỳ chọn dùng một mô hình khác, gỡ bỏ sự lạc quan của bên tạo, vì cùng lý do bạn không tự merge PR của chính mình mà không qua ai. Bên tạo đặt cược vào chuyện được xong; bộ kiểm chứng chỉ quan tâm điều kiện đã đạt hay chưa. Tách hai vai là nước đi lợi nhất trong thiết kế vòng lặp, và nó là người anh em nội bộ của chạy code review trên đầu ra của agent.
Trên thực tế bộ kiểm chứng thi hành một trong hai trường phái đã nêu ở đầu bài, lý tưởng là cả hai: nó chạy một tạo phẩm máy kiểm được (một bài kiểm tra, một lần lint, một lần kiểm lược đồ) và, ở một vòng lặp nấc 3, để một mô hình riêng chấm điều kiện. continuous-claude đóng gói đúng hình này với một lượt duyệt tuỳ chọn và một cờ --completion-signal; ralph mã hoá nó thành chốt passes: true mà một agent ngữ cảnh sạch phải đạt ở mỗi vòng.
Có một trục thiết kế thật sự đáng biết trước khi bạn ráp. Phần mở rộng ralph-wiggum chính thức chạy bộ kiểm chứng trong cùng phiên với bên tạo, và cộng đồng đã chỉ ra rằng cách này lệch khỏi Ralph ngữ cảnh sạch, nơi mỗi vòng (cả bên tạo lẫn bộ kiểm chứng) đều bắt đầu sạch. Cùng phiên thì rẻ hơn; ngữ cảnh sạch thì khó qua mặt hơn. Hãy chọn có chủ đích.
Một bộ kiểm chứng chỉ là một file agent. Hình mẫu dùng lại được:
---name: stop-condition-checkerdescription: Grades whether the loop's stop condition holds. Not the maker.model: haiku---You verify, you do not fix. Run the project's check command and report only:- PASS if `bash tests/run.sh` exits 0 AND `bash scripts/lint.sh` exits 0- FAIL otherwise, with the first failing lineNever edit code. Never report PASS on the maker's say-so; run the command.Điểm mấu chốt: Giá trị của bộ kiểm chứng đến trọn vẹn từ chỗ nó không phải là bên tạo. Chỉ dẫn khác, một mô hình rẻ hơn, và một ngữ cảnh sạch ở mỗi lượt là những thứ làm cho chữ PASS của nó có nghĩa.
Thực hành: lặp một agent cho tới khi một bài kiểm tra thật đạt
Để cho cụ thể, tôi đã chạy một vòng lặp thật trên một lỗi thật trong chính kho mã plugin content-agent này. Lỗi: scripts/check-draft-seo.sh kiểm xem từ khoá chính có được đặt lên đầu tiêu đề không, nhưng nó so từng từ của từ khoá với chỉ ba từ đầu của tiêu đề. Mọi từ khoá dài hơn ba từ đều bị báo sai, ngay cả khi nó dẫn đầu tiêu đề nguyên văn.
Trước hết là bài kiểm tra thất bại (phần đỏ). Dữ liệu mẫu (fixture) dùng từ khoá 6 từ “how to build an agent loop”, vốn dẫn đầu tiêu đề của nó và cũng nằm trong mô tả meta lẫn một H2, nên bài kiểm này lẽ ra phải đạt. Nó đã không:
⚠ keyword_placement: keyword "how to build an agent loop": not in title first 3 words
FAIL: multi-word keyword that leads the title was wrongly flagged.exit=1Điều kiện dừng là một điều kiện tốt theo bài kiểm tốt-và-tồi ở phần trước, máy kiểm được và bác bỏ được: bash tests/test-check-draft-seo-multiword-keyword.sh thoát 0 VÀ bash scripts/test-rubric.sh vẫn thoát 0. Vế thứ hai chính là chốt “không bài nào khác hỏng”.
Một lưu ý về nấc 3 so với cái tôi thật sự chạy. /goal là lựa chọn tự nhiên ở đây, và theo tài liệu thì nó sẽ để Haiku chấm điều kiện ở mỗi lượt. Nhưng /goal là một bộ chấm tương tác ngay trong phiên, mà tôi muốn một lần chạy ghi lại được và tái lập được, nên tôi dùng bản tương đương ở nấc 1: một claude -p ngữ cảnh sạch làm bên tạo ở mỗi lượt, và mã thoát của bài kiểm tra bash làm bộ kiểm chứng. Đó là một sự tách bên-tạo và bộ-kiểm-chứng trung thực, chấm bằng một tạo phẩm máy kiểm được chứ không phải bằng Haiku. Nếu bạn muốn bản Haiku chấm, đó chính là /goal.
Vòng lặp, giới hạn ở 8 lượt, đạt ngay lượt 1. Bên tạo ngữ cảnh sạch đọc bài kiểm tra thất bại, định vị cái cửa sổ ba từ cứng nhắc, và đổi sang lấy cửa sổ tiêu đề bằng max(3, len(kw_words)), nhờ vậy một từ khoá dài hơn mà dẫn đầu tiêu đề vẫn được nhận ra trong khi từ khoá ngắn giữ nguyên hành vi cũ. Thay đổi về chức năng rất nhỏ (7 dòng thêm, 2 dòng xoá, phần lớn là một dòng chú thích):
all_title_words = re.findall(r"[A-Za-z...]+", title.lower())kw_words = re.findall(r"[A-Za-z...]+", kw_lower)title_words = all_title_words[:max(3, len(kw_words))]📊 Bằng chứng kết quả. Một lượt bên tạo ngữ cảnh sạch. Đạt ngay lượt 1 (giới hạn 8 chưa bao giờ chạm tới). Sau lượt đó: bài kiểm tra hồi quy thoát 0,
scripts/test-rubric.shvẫn thoát 0, và một bản nháp đối chứng có từ khoá thật sự không đặt lên đầu vẫn báo cảnh báo đúng, nên bản sửa là thật, không phải một bài kiểm giờ luôn đạt. Lượng token và chi phí không được đo cho lần chạy này, nên tôi sẽ không trích một con số mà mình không có.
Đó là toàn bộ bài học, và nó ngắn có chủ đích: giá trị nằm ở điều kiện dừng kiểm chứng được, không phải ở một nhật ký dài. Một điều kiện tồi thì sẽ buộc tôi đọc bản khác biệt rồi tự quyết; ở đây bài kiểm tra quyết hộ.
Điểm mấu chốt: Vòng lặp dừng vì một lệnh thoát 0, không phải vì agent thấy mình đã xong. Đó là kiểu “xong” duy nhất bạn có thể để vòng lặp tự chạy một mình.
Làm sao giới hạn để vòng lặp không chạy mãi?
Mọi vòng lặp đều phải có giới hạn, vì một điều kiện dừng có thể sai hoặc không bao giờ đạt tới. Cách làm đã hội tụ qua các bài viết nghiêm túc năm 2026 là ba chốt dừng cứng: một giới hạn số vòng tối đa, phát hiện không tiến triển (trì trệ), và một trần token hoặc chi phí. Không hệ thống chững chạc nào chạy mà không giới hạn.
Mỗi chốt ứng với một cơ chế cụ thể. Giới hạn số vòng là claude -p --max-turns N cho một lần chạy không tương tác, cộng cơ chế tự ghi đè sau 8 lần chặn của Stop hook ở nấc 2. Phát hiện trì trệ là --stall-threshold của continuous-claude, nó kết liễu một vòng lặp đã thôi tiến triển. Trần ngân sách là --max-cost và --max-duration trên cùng công cụ đó. Hãy ráp ít nhất một chốt từ mỗi nhóm.
Lấy một tiền lệ có thật: chính vòng lặp duyệt nội dung của chúng tôi giới hạn ở 3 lượt. Một vòng lặp kiểm chứng kỹ lưỡng làm trong nhà vẫn chọn một con số cứng thay vì “cho tới khi hoàn hảo”, vì lựa chọn còn lại là một vòng lặp đánh bóng mãi không thôi. Danh sách kiểm điều kiện dừng của bạn, tạo phẩm dùng lại được từ bài này, gói trong ba dòng:
- Kiểm chứng được: có thứ gì đó xác nhận được (một lệnh, một con số, một mô hình thứ hai).
- Bác bỏ được: nó có thể thất bại, kèm một tín hiệu thất bại rõ ràng.
- Rẻ: bạn đánh giá được nó ở từng lượt một.
Điểm mấu chốt: Điều kiện dừng nói khi nào dừng vì đã thành công. Giới hạn nói khi nào dừng dù sao đi nữa. Một vòng lặp bạn tin tưởng có cả hai.
Câu hỏi thường gặp
Điều kiện dừng kiểm chứng được là gì?
Là một điều kiện mà một mô hình khác hoặc một bài kiểm bằng máy xác nhận được, ví dụ bài kiểm tra đạt, lint sạch, một lược đồ hợp lệ, hay passes: true trong một file, chứ không phải lời tự nhận của bên làm việc rằng nó đã xong. Nó kiểm chứng được, bác bỏ được, và đủ rẻ để đánh giá ở mỗi lượt.
/goal và Stop hook khác nhau thế nào?
Stop hook chặn hoàn tất bằng exit 2 cho tới khi một bài kiểm đạt, và Claude Code tự ghi đè sau 8 lần chặn liên tiếp. /goal (v2.1.139+) thì để một mô hình riêng, Haiku, chấm điều kiện dừng ở mỗi lượt. Hook là một chốt bạn tự viết; /goal là một bộ chấm bạn giao việc cho.
Làm sao để một vòng lặp agent không chạy mãi?
Giới hạn nó theo ba cách: một giới hạn số vòng tối đa (claude -p --max-turns N, cộng cơ chế ghi đè sau 8 lần chặn của Stop hook), phát hiện không tiến triển (--stall-threshold), và một trần token hoặc chi phí (--max-cost, --max-duration). Đó là ba chốt dừng cứng đã hội tụ.
Vì sao không nên để agent tự chấm việc của mình?
Vì một bên tạo tự chấm sản phẩm của mình luôn lạc quan một cách có hệ thống và tuyên bố “xong” quá sớm, đúng kịch bản thất bại thắng-lợi-sớm đã được ghi nhận. Hãy dùng một bộ kiểm chứng có chỉ dẫn khác, tuỳ chọn một mô hình khác, và lý tưởng là một ngữ cảnh sạch ở mỗi lượt.
Đọc tiếp
- Phần 4: Bộ nhớ nằm ngoài cửa sổ ngữ cảnh (
loop-memory-state-files, sắp ra trong loạt Loop Engineering). Một chữ “xong” đã kiểm chứng cũng vô nghĩa nếu vòng lặp quên mất nó giữa các lần chạy, nên Phần 4 dựng cái trục tệp trạng thái giúp một điều kiện dừng còn lưu lại. - Vòng lặp tự kiểm chứng, phần tiền truyện trong một phiên mà phần này tổng quát hoá thành một điều kiện dừng xuyên nhiều lần chạy.
- Giải phẫu một vòng lặp, Phần 2, nơi khối bộ kiểm chứng được gọi tên rồi hẹn lại tới đây.