blog
Loop engineering: thôi prompt agent, đi thiết kế cái vòng lặp prompt nó
Dựng một vòng lặp cho agent giờ mất mười lăm phút. Phần khó là nhét vào trong đó một thứ dám nói "không". Năm nước đi của một vòng, năm kiểu hỏng, và vòng lặp thật của site này đang đi được nửa đường.
Dựng một vòng lặp cho agent giờ mất mười lăm phút: một dòng cron, một skill, một lệnh chạy. Phần khó là nhét vào trong đó một thứ dám nói "không".
Tháng 6/2026 có ba người gọi tên cùng một kiểu làm việc trong đúng một tuần. Peter Steinberger viết là đừng prompt coding agent nữa, hãy thiết kế vòng lặp prompt nó. Boris Cherny, người dẫn Claude Code, nói anh không còn prompt Claude mà có sẵn các vòng lặp tự prompt Claude và tự tìm việc tiếp theo. Addy Osmani đặt tên và viết nó ra ngày 7/6/2026: loop engineering là thay chính mình ở vị trí người prompt agent, rồi thiết kế cái hệ thống làm việc đó thay mình.
Loop nằm một tầng trên harness, khác ở ba động từ
Bốn tầng này chồng lên nhau chứ không thay thế nhau. Prompt lo câu chữ trong một lượt. Context lo cái gì được nạp vào cửa sổ ngay lúc này. Harness lo trang bị cho một lần chạy: tool nào, hành động nào được phép, thế nào là xong. Loop lo phần còn lại: làm sao nó tự chạy lại, hết lần này tới lần khác.
Harness kết thúc ở chỗ một lần chạy xong rồi đứng đó đợi. Ba động từ tách loop khỏi harness:
- Chạy theo giờ. Vòng lặp tự thức dậy, không cần ai bấm nút.
- Đẻ sub-agent. Một vòng đang quay tách ra nhiều agent con, đứa viết code, đứa chỉ soi lỗi.
- Tự ăn output của mình. Kết quả hôm nay ghi ra file, sáng mai nó đọc lại file đó rồi làm tiếp.
Động từ thứ ba là thứ khiến nó thành vòng lặp thay vì một task chạy nhiều lần.
Mỗi tầng hỏng một kiểu khác nhau, và đây mới là lý do phải phân tầng. Lấy cùng một lỗi: agent đọc sai giá trị trả về của một hàm. Ở tầng prompt, nó cho một câu trả lời sai và người ngồi đó sửa ngay. Ở tầng harness, agent sửa một file rồi kết thúc, diff nằm đó chờ review. Ở tầng loop, cái đọc sai ấy được ghi vào file state, sáng hôm sau đọc lại như một dữ kiện đã chốt, rồi mọi thứ xây tiếp lên nó. Tới lúc có người nhìn thì giả định sai đã thành chỗ chịu lực. Chi phí của một lỗi tỉ lệ với số lượt nó sống sót trước khi bị bắt, và vòng lặp về bản chất là cỗ máy tăng số lượt lên tối đa.
Năm nước đi trong một lượt: discovery, handoff, verification, persistence, scheduling
Bỏ một trong năm thì vòng không quay, hoặc quay tại chỗ.
| Nước đi | Làm gì | Hiện thực bằng |
|---|---|---|
| Discovery | Tự tìm việc của lượt này | Một skill đọc CI, issue, commit mới |
| Handoff | Giao việc, mỗi việc một chỗ riêng | git worktree cho từng agent |
| Verification | Đổi agent khác vào để nói "không" | Sub-agent review, model khác |
| Persistence | Ghi trạng thái ra ngoài cuộc hội thoại | File markdown hoặc board, có commit |
| Scheduling | Làm nó quay lượt sau | cron, GitHub Actions, automation |
Discovery đặt trần cho cả vòng lặp. Tìm ra toàn việc vô giá trị thì bốn nước còn lại làm đẹp tới đâu cũng phục vụ cho hư không. Phần logic tìm việc nên nằm trong một skill có tên, đừng dán cả một khối chữ vào crontab, vì file skill còn được sửa còn khối chữ trong crontab thì mục rữa ở đó.
Osmani liệt kê năm thành phần: automations, worktrees, skills, connectors, sub-agents. Tôi thêm thành phần thứ sáu là memory, tức trạng thái nằm trên đĩa. Memory khác context: context là cái agent nhìn thấy lượt này và bị xoá khi refresh, memory sống qua nhiều ngày. Agent quên, repo thì không.
Agent chấm bài của chính nó thì luôn cho điểm cao
Đây là chỗ mọi vòng lặp chết. Bảo agent tự đánh giá thứ nó vừa viết ra thì nó khen, khen chắc nịch, kể cả khi người ngồi cạnh nhìn phát biết là tầm thường. Kỹ sư Anthropic Prithvi Rajasekaran gặp đúng chuyện đó khi dựng các ứng dụng agent chạy dài.
Nguyên nhân nằm ở context. Cửa sổ lúc đó đã đầy ắp lý do vì sao code được viết như vậy, nên khi agent nhìn lại output của mình, nó không nhìn thấy kết quả, nó nhìn thấy chuỗi lập luận đã dẫn nó tới đó.
Cách sửa không nằm ở câu chữ. Bắt generator tự khắt khe hơn thì gần như vô ích, còn tinh chỉnh một evaluator độc lập cho nó đa nghi lại dễ hơn nhiều. Không ai bước ra khỏi góc nhìn của chính mình được, nhưng đổi hẳn một agent khác vào, với bộ chỉ dẫn khác, nhìn code từ con số không thì được.
# .claude/agents/reviewer.md
ROLE: Adversarial code reviewer.
ASSUME: this code is BROKEN until proven otherwise.
DO NOT praise. Find what fails.
CHECK, in order:
1. Does it run? (execute, don't read)
2. Tests: run them, paste real output.
3. Edge cases the author skipped.
4. Does behavior match the ticket?
USE Playwright MCP: open the page, click, screenshot, inspect the DOM.
Judge behavior, not intent.
VERDICT: PASS only if every check holds. Otherwise REJECT + list each reason.
Hai chi tiết trong file trên đáng chú ý hơn phần còn lại. Evaluator phải hành động, không chỉ đọc. Chỉ đọc thì nó đánh giá "trông có vẻ đúng" chứ không phải "chạy có đúng không"; nối nó vào Playwright MCP để mở trang, bấm nút, chụp màn hình thì căn cứ chấm đổi từ "JSX này nhìn ổn" thành "tôi bấm nút, trang chuyển, ảnh đây". Nên đổi cả model bên dưới, vì cùng một model với chỉ dẫn mới thường giữ nguyên điểm mù cũ.
Điều kiện dừng cũng nên giao cho một model khác. Tài liệu Claude Code mô tả /goal là "Claude keeps working across turns until the condition is met". Sau mỗi lượt có một model nhỏ và nhanh kiểm xem điều kiện đã đạt chưa; chưa đạt thì chạy tiếp lượt nữa thay vì trả quyền về. Đây là nguyên tắc maker-checker của ngành ngân hàng, người nhập lệnh chuyển tiền lớn và người duyệt phải là hai người, đem áp cho câu hỏi "xong chưa".
/goal all tests in test/auth pass and the lint step is clean
Trình độ generator quyết định vòng lặp làm ra được cái gì, còn trình độ evaluator quyết định nó không cho cái gì lọt ra, nên sàn chất lượng của cả vòng nằm ở evaluator.
Vòng lặp của site này mới đi được nửa đường
Repo của nguyentrungkhuong.com đang chạy một vòng lặp thật, nhỏ, và tôi biết chính xác nó thiếu gì.
Nước đi có sẵn: persistence là 10 file .sources.yml nằm cạnh 10 bài blog, tổng 102 claim, mỗi claim ghi số nào lấy từ nguồn nào. Verification là check-sources.js, 280 dòng, chạy ở prebuild, đối chiếu mọi số có hình dạng dữ liệu trong bài với sidecar và thoát khác 0 nếu lệch:
"prebuild": "npm run check:sources && node scripts/generate-search-index.js && ..."
Đặt nó ở prebuild là cố ý. Nó không phải một cảnh báo trong log, nó chặn npm run build, nên deploy đỏ và site giữ nguyên bản cũ. Đây đúng là "thứ dám nói không", và giá của nó là 280 dòng JavaScript không phụ thuộc gì.
Scheduling là một job cron chạy 08:00 thứ hai hàng tuần:
on:
schedule:
- cron: '0 1 * * 1'
Discovery nằm trong job đó: nó gom mọi link ngoài trong content/, curl từng cái xem còn sống không, rồi soi những claim đánh dấu volatile có ngày kiểm quá 90 ngày.
Thiếu handoff. Job cron tìm ra vấn đề rồi đổ vào ::warning:: của Actions, chờ tôi đọc. Không có worktree nào mở ra, không agent nào được giao việc, không PR nào được tạo. Nó tự tìm việc và tự chặn việc sai, nhưng không tự làm, nên mới là nửa vòng lặp.
Đợt rà tháng 7/2026 chạy tay qua 10 bài tìm ra 7 lỗi thật, trong đó có một câu gán cho tài liệu Chrome một phát biểu mà tài liệu đó không hề nói. Cái sidecar không ngăn được việc viết sai câu ấy. Nó làm việc viết sai kiểm được, và đó là toàn bộ giá trị của nó.
Bước tiếp theo đã rõ: cho job thứ hai mở worktree cho từng phát hiện, giao một agent soạn bản sửa, và giữ nguyên chỗ dừng ở PR. Không auto-merge.
Năm kiểu vòng lặp hỏng, mỗi kiểu là một nước đi bị bỏ
| Kiểu | Bỏ nước đi | Triệu chứng |
|---|---|---|
| Gật gù | Verification | Chạy hàng trăm lượt chưa từng nói "không" lần nào |
| Mất trí | Persistence | Sáng nào cũng bắt đầu lại từ chỗ cũ |
| Thủ công | Scheduling | Lần chạy gần nhất là hôm đem đi demo |
| Mù | Discovery | Người vẫn mất buổi sáng để quyết xem vòng lặp nên làm gì |
| Rối | Handoff | Một agent thì ổn, sáng nào chạy năm agent là merge không gỡ nổi |
Kiểu gật gù phổ biến nhất. Một vòng lặp chạy hàng trăm lượt mà chưa lần nào tự bác bỏ chính nó là chuyện không thể có với bất kỳ khối lượng công việc thật nào, nên nó là bằng chứng rằng không có cái kiểm nào tồn tại ở đó.
Năm kiểu này hay đi cùng nhau. Vòng lặp làm vội thường cài đúng hai nước tạo ra kết quả nhìn thấy được, discovery và handoff, rồi bỏ ba nước tạo ra an toàn.
Bốn khoản nợ không kêu tiếng nào
Vòng lặp tự chạy cũng là vòng lặp tự sai. Không khoản nợ nào dưới đây phát ra tiếng trong lúc nó đang chạy.
Verification debt. Mỗi PR mở ra và merge vào đều tiết kiệm thời gian, và thời gian tiết kiệm được biến thành output chưa ai kiểm, nằm đợi ngày trả. Nó trốn ở đúng chỗ test không phủ, trong khoảng cách giữa "chạy được" và "đúng".
Comprehension rot. Codebase phình ra trong khi bản đồ trong đầu đứng yên. Đọc code chán hơn viết code, mà phần viết thì vòng lặp lấy mất rồi.
Cognitive surrender. Vòng lặp càng đáng tin thì càng dễ thôi có ý kiến, nhận cái nó đưa rồi đi. Đây là phiên bản thái độ của hai khoản trên: hết muốn bận tâm, trong khi thời gian vẫn còn nguyên.
Token blowout. Khoản duy nhất đánh thẳng vào hoá đơn. Một con bug quay không tải cả đêm thì sáng ra có hoá đơn lạ chứ không có code đã sửa.
Bốn khoản này nuôi nhau. Một vòng lặp mở 20 PR qua đêm, test xanh hết, nhìn qua là thắng lớn. Giả sử ba trong hai mươi PR chứa lỗi tinh vi mà test không chạm tới: không evaluator độc lập, ba cái đó merge, đấy là verification debt. Người merge hai mươi PR không đọc, bản đồ trong đầu lùi lại hai mươi thay đổi, đấy là comprehension rot. Vòng chạy êm quá nên sáng hôm sau thôi đọc luôn, đấy là cognitive surrender. Và vì nó thoải mái đẻ helper với retry suốt đêm, hoá đơn gấp ba dự tính, đấy là token blowout.
Ba cái chốt tương ứng, đặt trước khi chạy chứ không phải sau hoá đơn đầu tiên:
- Đọc mẫu mỗi ngày, không đọc hết, nhưng buộc mình giải thích lại vài thay đổi đã đọc. Giải thích không nổi nghĩa là bản đồ trong đầu đã tụt lại.
- Đặt trần cứng: ngân sách mỗi lần chạy, ngân sách mỗi ngày, số retry tối đa. Mấy con số này là cầu chì, không phải mẹo tiết kiệm.
- Chừa một cửa mở, tức ít nhất một điểm vòng lặp dừng lại chờ người. Không phải vì lần nào cũng cần can thiệp, mà vì cái cửa đó giữ cho người còn ở tư thế can thiệp được.
Stripe merge hơn 1.300 PR mỗi tuần bằng gate cứng, không bằng model to hơn
Hệ thống Minions của Stripe merge hơn 1.300 pull request mỗi tuần, không dòng nào gõ tay, theo lời kỹ sư Steve Kaliski trên podcast How I AI. Trigger nhẹ tới mức buồn cười: tag bot trong Slack, hoặc thả một emoji reaction.
Phần làm nên độ tin cậy nằm ở đoạn trước khi model thức dậy. Một orchestrator xác định gom context trước: quét link, kéo Jira, tìm docs, dùng Sourcegraph và MCP để định vị code liên quan. Để LLM tự đi tìm context của nó là phần khó kiểm soát nhất, nên phần việc đó, vốn có thể viết thành luật, bị lấy ra khỏi tay model.
Chi tiết ngược đời nhất: Minions không dựng trên một model mạnh hơn. Nó là bản fork của Goose, một tool nguồn mở, và luận điểm của nó là độ tin cậy đến từ chất lượng các ràng buộc chứ không từ kích thước model. Kiến trúc cài xen kẽ gate xác định với bước sáng tạo của LLM: agent viết code, một pipeline cứng chạy linter mà agent không có cách nào bỏ qua, agent sửa lint, rồi một bước cứng nữa chạy commit.
1.300 PR đó vẫn do người review, tức người không rời đi mà đổi từ bàn viết sang bàn duyệt.
Dựng vòng lặp đầu tiên: chọn local hay cloud trước đã
Câu hỏi duy nhất cần trả lời: việc của vòng lặp có dính vào máy local không?
/loop trên máy | Lịch cloud | |
|---|---|---|
| Máy phải bật | Có | Không |
| Session phải mở | Có | Không |
| Chu kỳ ngắn nhất | phút | giờ |
| Thấy file local | Có | Không |
Vòng lặp phải ping dev server mỗi phút thì chỉ chạy local được, vì cloud không nhìn thấy process trên laptop. Vòng lặp quét issue lúc ba giờ sáng thì đừng buộc vào laptop, vì laptop bị gập nắp, hết pin và bị xách ra khỏi nhà.
Chỗ hay nhầm là coi việc chạy lại tại chỗ là "chạy trong lúc mình ngủ". /loop chạy trong session, theo mô tả trong tài liệu là "Run a prompt repeatedly while the session stays open". Nó nghĩa là "chạy thêm mấy vòng nữa trong lúc tôi còn ngồi đây". Lịch cloud mới nghĩa là "chạy cả khi tôi không ở đây".
Với người làm web ở Việt Nam thì phần này rẻ hơn nghe tưởng. Ai đang thuê VPS chạy site là đã có sẵn một máy không bao giờ tắt. Repo này cài GitHub Actions self-hosted runner ngay trên server đó cho pipeline deploy, và cùng cái runner ấy chạy luôn job cron kiểm nguồn hàng tuần. Không phát sinh thêm hạ tầng, không tốn phút Actions.
Ba lệnh đủ để bắt đầu, kiểm trên bản Claude Code 2.1.196:
# chạy lặp trong session, tự giãn nhịp nếu bỏ trống khoảng thời gian
/loop 5m check if the deploy finished
# chạy tới khi điều kiện đạt, có model khác chấm điều kiện
/goal all tests in test/auth pass and the lint step is clean
# mỗi việc một worktree riêng, agent song song không giẫm chân nhau
claude --worktree fix/auth-test "draft the fix"
Sáu dòng checklist trước khi thả cho nó chạy một mình:
- Discovery: nó đọc gì theo lịch? CI, issue, commit hay inbox
- State: file nào trên đĩa giữ trí nhớ giữa các lượt
- Evaluator: có cái kiểm độc lập nào nói "không" được không
- Isolation: mỗi agent song song có worktree riêng chưa
- Token cap: đã đặt trần chi chưa, ai dừng nó khi nó chạy hoang
- Human review: bước nào dừng lại chờ người nhìn
Hai dòng đầu quyết định vòng lặp chạy được hay không. Bốn dòng sau quyết định nó có gây hoạ khi đã chạy hay không. Người mới thường ship với đúng hai dòng đầu.
Đánh đổi thật khi giao việc cho vòng lặp
Vòng lặp là một dấu nhân trung thành: mang hiểu biết vào thì nó nhân hiểu biết, mang lười vào thì nó nhân lười lên đúng chừng ấy lần. Hai người dựng cùng một vòng lặp, code giống nhau tới chín mươi phần trăm, khác một hai chỗ dừng, và sáu tháng sau một người đứng trên vòng lặp còn người kia thành người gác cổng cho một cỗ máy họ không đọc được nữa.
Chỗ site này chưa làm được, nói thẳng: gate của tôi chỉ đối chiếu số. Ba loại sai nguy hiểm nhất vẫn phải đọc bằng mắt, gồm claim định tính sai, gán số đúng cho sai nguồn, và trích một tài liệu có thật những câu nó không nói. Cả ba đều nằm trong 7 lỗi đợt rà vừa rồi, và không cái nào máy bắt được.
Vòng lặp làm phần sinh ra gần như miễn phí. Thứ còn khan hiếm là biết cái nào đúng, và đó là lý do vẫn cần một kỹ sư ngồi đây.
Bài viết liên quan
tất cả bài viết →- Bỏ commit thư mục out/: dựng CI/CD build static ngay trên serverRepo này có 29 trên 51 commit đụng vào thư mục build output. Cách bỏ hẳn nó: self-hosted runner build ngay trên server, publish bằng một thao tác đổi symlink, purge Cloudflare, và ba cái bẫy bash chỉ lộ ra khi đem ra test.đọc tiếp →
- codebase-memory-mcp: dựng knowledge graph cho codebase và những lưu ýMột binary C index cả repo thành knowledge graph để agent hỏi bằng graph query thay vì grep từng file. Cách cài, ba tool dùng nhiều nhất, và hai lỗi im lặng làm graph thiếu mà vẫn báo indexed.đọc tiếp →
- Hy3 của Tencent — model 295B chạy với chi phí của model 21BHy3 có 295 tỷ tham số nhưng mỗi token chỉ chạy qua 21 tỷ. Kiến trúc MoE, điểm benchmark đặt cạnh GLM-5.2, giá sau khi hết bản free, và việc nào nên giao cho nó.đọc tiếp →
- Gemini 3.6 Flash trên Antigravity CLI — ít hơn 17% output tokenGoogle thay Gemini CLI bằng Antigravity CLI, và 3.6 Flash là model đáng để mặc định trong terminal. Cách chọn model bằng agy, chạy headless, và tính tiền cho một lượt agent.đọc tiếp →