Skip to content
EngineeringOctober 26, 2026

Agent ship AnyRouter thế nào: vòng lặp merge và deploy

Coding agent viết, review, merge và deploy phần lớn thay đổi của AnyRouter. Trong tuần 19 đến 25 tháng 10 năm 2026, vòng lặp đó đã merge 346 pull request. Bài đầu tiên của loạt bài này mô tả vòng lặp: cổng CI chạy local có chữ ký, một reviewer cho mỗi commit, một script merge duy nhất, và deploy có kiểm tra khoảng mỗi giờ một lần.

Agent làm phần lớn việc ship

AnyRouter là một team nhỏ cùng nhiều coding agent. Agent nhận issue, viết bản sửa trong git worktree riêng, mở pull request, review pull request của nhau, merge những cái đã được duyệt và deploy. Chủ dự án đặt luật và quyết những việc chỉ người mới nên quyết.

Loạt bài này kể cách việc đó chạy trong thực tế: cái gì đã tự động hoá, cái gì đã hỏng, và chúng tôi đổi gì sau khi nó hỏng. Mọi con số đều lấy từ lịch sử git, GitHub API hoặc các script trong repo.

Trong tuần 19 đến 25 tháng 10 năm 2026, 346 pull request đã merge vào main. Ngày nhiều nhất có 71, ngày ít nhất có 29. Trong ba tuần từ 5 đến 25 tháng 10, có 1.111 pull request đã merge.

Khối lượng đó chỉ chạy được khi mọi bước từ một commit được push tới một Worker đang chạy là một script có cổng kiểm tra, không phải một người bấm nút. Bài này đi qua vòng lặp đó.

Toàn cảnh vòng lặp

flowchart TD
I["Issue"] --> L["Lane agent trong worktree riêng"]
L --> PR["Pull request"]
PR --> CI["pnpm local-ci ký commit"]
CI --> RV["Reviewer agent mới ký cùng commit đó"]
RV -->|"yêu cầu sửa"| L
RV -->|"duyệt"| M["merge-pr.sh squash-merge"]
M --> Q["Thay đổi đã merge chờ lần deploy sau"]
Q --> G{"Cổng bộ nhớ, ổ đĩa và lease"}
G -->|"chưa"| Q
G -->|"thông"| D["Bookmark, smoke, pnpm ops deploy, smoke"]
D -->|"smoke lỗi mới"| R["Rollback các Worker vừa upload"]
Một vòng của vòng lặp. Một commit mới trên pull request sẽ bắt đầu lại từ cổng CI, vì cả hai chữ ký đều gắn với một commit.

Phần còn lại của bài đi qua từng ô.

GitHub Actions đã tắt, nên CI chạy trên máy của chúng tôi

Chúng tôi tắt GitHub Actions CI để tiết kiệm tiền. Chỉ còn các workflow release chạy ở đó. Cổng kiểm tra chuyển sang một lệnh local, pnpm local-ci <pr> (scripts/local-ci.ts), có từ ngày 4 tháng 10 năm 2026.

Lệnh này chạy cùng các kiểm tra mà workflow cũ chạy, lần lượt từng cái, vì máy chỉ có 15 GB bộ nhớ và không chịu được build song song. 18 guard của repo luôn chạy. Chúng kiểm tra những thứ như số thứ tự migration, lệch schema, ranh giới import giữa các app, config sinh ra bị cũ, và định dạng mô tả pull request. Phần còn lại tuỳ theo thay đổi: biome trên các file đã đổi, chỉ những chương trình typecheck mà thay đổi chạm tới, và vitest related trên code đã đổi.

Hai chi tiết làm kết quả đáng tin.

  • Các kiểm tra chạy trên pull request đã merge vào main hiện tại, không chỉ trên nhánh. Nếu không, một nhánh cũ sẽ bỏ qua kiểm tra được thêm vào main sau khi nhánh tách ra.
  • Kết quả đạt được đăng thành commit status local-ci bởi một tài khoản bot duy nhất. Status ghi số cổng và sha256 của log, nên một kết quả xanh trỏ đúng vào output đã tạo ra nó. Status từ tài khoản khác không được tính.

Một status thật từ một pull request gần đây ghi 41/41 gates passed (scoped, on main 3e175c42). Con số thay đổi theo phần mà mỗi pull request chạm tới.

Một reviewer cho mỗi commit

CI chứng minh code build được và test qua. Nó không chứng minh thay đổi là đúng. Vì vậy mỗi pull request còn được review bởi một phiên agent mới, không phải phiên đã viết code. Reviewer đọc diff theo checklist kiểm tra của chúng tôi, chạy lại những gì nó nghi ngờ, rồi duyệt hoặc trả lại kèm danh sách sửa cụ thể.

Lượt duyệt gắn với một commit, không gắn với pull request. scripts/herdr/approve-pr.sh nhận số pull request và đúng commit mà reviewer đã đọc. Nếu có ai push trong lúc review, script từ chối, vì head hiện tại không còn là code reviewer đã xem. Khi thành công, nó đăng status review trên commit đó và gắn nhãn review:approved.

Trong 346 pull request merge từ 19 đến 25 tháng 10, 342 cái có nhãn review:approved.

Chỉ một cách để merge

Mọi lần merge đi qua scripts/herdr/merge-pr.sh. Script từ chối pull request release, vì chủ dự án merge chúng bằng tay. Nó từ chối head không có kết quả local-ci đạt có chữ ký. Sau đó nó chạy check:pr-merge-safety, kiểm tra thêm status review có chữ ký và nhãn duyệt trên head hiện tại, và từ chối pull request mang nhãn review:changes.

Chỉ khi đó nó mới squash-merge, với --match-head-commit, để một lần push xảy ra giữa lúc merge làm merge thất bại thay vì ship code chưa review.

Script này ra đời vì một lỗi ở phiên bản trước của bước này. Lệnh kiểm tra merge được pipe sang lệnh khác, và một pipeline shell trả về exit code của lệnh cuối, nên một kiểm tra thất bại vẫn để một pull request đỏ được merge. Giờ lệnh kiểm tra chạy riêng, và script có một comment ghi lý do.

Deploy, khoảng mỗi giờ một lần

Không có gì deploy khi push. Một vòng lặp polling (scripts/gh-loop/) kiểm tra GitHub mỗi 3 phút. Khi có thay đổi đã merge từ lần deploy trước và đã qua ít nhất 30 phút, nó mở một lane deploy. Lane chờ đến khi máy có ít nhất 5,5 GB bộ nhớ khả dụng và 20 GB ổ đĩa trống, và chạy tối đa 3 lane nặng cùng lúc.

Bản thân lần deploy đi theo thứ tự cố định.

  1. Checkout main vừa fetch trong một worktree chỉ dùng để deploy.
  2. Ghi một bookmark D1 Time Travel, để có thể khôi phục database về đúng phút đó.
  3. Chạy smoke test production để lấy mốc so sánh.
  4. Chạy pnpm ops deploy. Lệnh giữ một deploy lease, nên hai máy không bao giờ deploy cùng lúc. Nó chỉ deploy các Worker có code thay đổi so với commit mà mỗi Worker đang chạy. Nó từ chối deploy một main cũ hơn bản đang chạy.
  5. Chạy lại smoke test. Một lỗi không có trong mốc so sánh sẽ rollback từng Worker mà lần deploy đã upload.

Bước 2 từng là một lần export toàn bộ database. Chúng tôi bỏ nó sau khi một lần export làm database ngừng phục vụ. Một lần export D1 chặn mọi truy vấn trong 2 đến 4 phút, và API lỗi suốt thời gian đó. Bookmark không gây gián đoạn. Bài sau sẽ kể sự cố đó và luật được tạo ra từ nó.

Ngày 26 tháng 10, log deploy trên một máy của chúng tôi ghi 15 lần deploy thành công và 2 lần thất bại trong khoảng từ 0 giờ đến 17 giờ UTC. Với 18 lần deploy thường thành công được ghi trong ngày 25 và 26 tháng 10, thời gian chạy trung vị là 520 giây. Một lần chạy đầy đủ chạm tới 17 Worker. Các máy khác cũng deploy, nên đây là một mẫu, không phải tổng.

Những việc vòng lặp không làm

Vòng lặp bỏ qua hoàn toàn một số thay đổi và để lại cho chủ dự án:

  • Pull request chạm tới migration database, billing và credit, xác thực, API key và key nhà cung cấp đã lưu, hoặc các trang pháp lý.
  • Migration xoá, đổi tên hoặc ghi lại dữ liệu. Loại này cần chủ dự án duyệt và một bản backup. Migration chỉ thêm mới thì tự áp dụng.
  • Pull request release, bản nháp và cập nhật dependency.
  • Mọi thứ liên lạc với khách hàng.

Mỗi luật bỏ qua là một dòng trong một bảng ở scripts/gh-loop/rules.ts, và mỗi dòng có một test sẽ fail nếu ai đó xoá nó.

Bài tiếp theo trong loạt bài

Bài sau kể cách sự cố trở thành guard: sự cố export database, một stash dùng chung giữa các worktree, và những lần merge đã đảo ngược code mới hơn. Sau đó là deploy theo từng Worker, chạy nhiều agent trên một máy 15 GB, và cách chúng tôi tìm model miễn phí.

Gửi request đầu trong hai phút

Bắt đầu miễn phí với key của bạn, hoặc nạp rồi trả theo token. Go: $4 credit/tháng — $2/tháng, hoặc miễn phí khi góp một provider key.

Bắt đầu miễn phí