TL;DR

  • Gắn tier blast radius (T0/T1/T2/T3) cho từng PR AI trước khi đọc dòng nào, quyết định trong dưới 30 giây dựa trên paths và CODEOWNERS, không tin label do agent tự gắn.
  • Phân bổ read budget ngược tỉ lệ với tier number: T0 đọc trọn line-by-line, T1 dùng checklist Part 1 rút gọn, T2 behavioral spot-check, T3 sample một chỗ rồi gate trên CI green.
  • Khi ba PR AI cùng giờ, triage tốn dưới 2 phút và bạn vẫn ship đúng chất lượng cho cái nguy hiểm nhất.

📊 Result Proof

  • Một bảng allocation áp được vào hộp PR thật: 8 PR/giờ → 1 T0 + 2 T1 + 3 T2 + 2 T3, T0 đọc ngay, T3 defer cuối ngày.
  • Lệnh gh pr view {N} --json files,labels chạy thật, output rút gọn dán được vào ticket review.
  • Behavioral spot-check protocol bốn bước, có verify step concrete cho mỗi bước.

Phần 1 đã dạy bạn một checklist sáu mục chạy ngon khi bạn có thời gian đọc trọn một PR, nhưng vỡ trận đúng cái khoảnh khắc ba PR AI khác đáp xuống cùng một giờ và bạn không thể đọc từng dòng của cả ba. Đây là cliffhanger Part 1 dừng lại, và Part 2 không phải là một checklist khác. Vấn đề không nằm ở checklist, vấn đề nằm ở read budget. Bài này dạy bạn gắn tier blast radius cho từng PR AI trước khi đọc dòng nào, phân bổ read budget theo tier, và spot-check tier thấp bằng behavioral diff thay vì line-by-line. Tới cuối bài, ba PR AI cùng giờ vẫn ship đúng chất lượng cho cái nguy hiểm nhất, còn cái docs-only không ăn mất quỹ thời gian của bạn.

Prerequisites:

  • Đã đọc Part 1 của series. Part 2 mặc định bạn biết checklist sáu mục và sẽ thu hẹp nó cho tier thấp.
  • Đang làm senior dev hoặc tech lead có quyền reject/approve PR.
  • Repo có ít nhất một signal về scope thay đổi (CODEOWNERS, label hệ thống, path-based ownership). Bài này dùng tín hiệu đó, không tự xây từ đầu.
Terminal window
# Kiểm tra nhanh signal có sẵn:
test -f CODEOWNERS && echo "CODEOWNERS ok" || echo "CODEOWNERS missing, dùng path heuristic"
gh pr list --limit 5 --json number,title,labels
# Expected: thấy danh sách PR + labels (kể cả label do agent tự gắn).

Ba PR AI cùng giờ: vì sao checklist Part 1 vỡ?

Checklist Part 1 không sai, nó chỉ giả định bạn có thời gian đọc trọn một PR. Khi đầu vào nhân ba, tổng thời gian đọc cũng nhân ba, nhưng read budget không co giãn. Phần lớn senior dev tôi biết bị bám vào ảo tưởng “đọc nhanh hơn”, và sau hai tháng họ approve quá vội hoặc defer cả luồng, cả hai đều tệ.

Để rõ hơn: read budget là thời gian và attention tối đa bạn dành đọc kỹ trong một ca review. Một ca có thể là buổi sáng, một slot giữa hai cuộc họp, hoặc một block 90 phút. Read budget hữu hạn, không bargain được bằng caffeine.

Điểm mấu chốt: Read budget là tài nguyên hữu hạn, không phải biến tăng theo nhu cầu. Khi luồng PR AI vượt budget, mọi quyết định review tốt phải bắt đầu bằng phân bổ, không phải bằng đọc.

Ba kiểu phản ứng sai khi luồng vượt budget:

Phản ứng saiHệ quảDấu hiệu
Đọc tuần tự rồi merge vộiCái cuối approve không nhìn diffApprove hai PR liên tiếp trong 5 phút
Đọc song song qua nhiều tabLẫn context, miss API misuseQuay lại tab 1 không nhớ vừa đọc gì
Defer tất cả tới chiềuQueue dài, dev mất niềm tinCuối tuần còn 12 PR AI chưa review

Triage ngược lại cả ba, vì nó tách “đọc bao nhiêu” khỏi “đọc cái nào trước”.

Tier hoá blast radius: T0/T1/T2/T3 nghĩa là gì?

Tier là phân loại PR theo mức ảnh hưởng tiềm tàng nếu PR đó sai sau merge, không phải theo độ phức tạp code. Một PR đổi 3 dòng IAM policy nguy hiểm hơn một PR đổi 300 dòng test mới. Tier cao đồng nghĩa blast radius lớn, không đồng nghĩa diff dài.

Điểm mấu chốt: Blast radius tier (T0–T3) là phân loại PR theo mức ảnh hưởng nếu PR sai sau merge, không phải theo size diff. Tin signal này, không tin label do agent tự gắn.

Bốn tier dùng cho mọi PR AI đến hộp của bạn:

TierMô tảVí dụDecision authority
T0Infra / security / dataDockerfile, IAM policy, schema migration, auth flowLead hoặc CTO sign-off, không solo
T1Core business logicRefactor checkout service, đổi pricing ruleSenior dev sign-off, pair khi có thể
T2Isolated featureEndpoint mới sau feature flag, view component mớiSenior dev solo, có behavioral test
T3Docs / tests / non-runtimeREADME, JSDoc, thêm unit test cho hàm đã có coverageSenior dev solo, CI gate đủ

Bốn tier là baseline tôi dùng cho team 8–15 người. Team lớn hơn có thể tách T1 theo bounded context; team nhỏ hơn có thể gộp T2 và T3, nhưng tôi không khuyên vì chúng cần phân bổ read budget khác nhau.

Tier KHÔNG phụ thuộc vào ai mở PR. Một PR T0 do junior viết tay vẫn là T0; một PR T3 do agent viết vẫn là T3. Tier ghim vào artifact, không ghim vào author.

Gắn tier cho một PR AI trong 30 giây như thế nào?

What: Mở PR. Không mở tab Files Changed. Nhìn ba thứ theo thứ tự: paths trong description, CODEOWNERS match, label hệ thống (kể cả label do agent tự gắn, để biết agent đang nghĩ gì).

Why: Coding agent (Claude Code, Cursor, Codex) hay tự gắn label sai về scope. Chúng thấy file .md và gắn “docs only” trong khi PR cũng touch một migration script ở chỗ khác trong diff. Tự verify nhanh hơn tin label.

Terminal window
# Production usage, không phải sandbox.
gh pr view 4271 --json files,labels,additions,deletions | \
jq '{labels: [.labels[].name], paths: [.files[].path], add: .additions, del: .deletions}'
# Expected output (rút gọn):
# {
# "labels": ["ai-authored", "docs-only"],
# "paths": [
# "docs/onboarding.md",
# "scripts/seed-dev-db.sh",
# "migrations/20260520_add_user_role.sql"
# ],
# "add": 142,
# "del": 8
# }

Label nói “docs-only” nhưng paths có migrations/. Đó là T1, không phải T3. Override label trong dưới 30 giây.

Verify: Bạn gán đúng một trong T0/T1/T2/T3 vào PR (label tier/t1 hoặc comment đầu) trong dưới 30 giây mà chưa mở Files Changed. Đo bằng đồng hồ một lần đầu tiên, sau hai tuần thành phản xạ.

Failure mode tôi đã sống qua: một PR agent gắn label “test only” vì tất cả file đều dưới tests/. Tôi tin label, gắn T3, defer. Hôm sau CI đỏ ở migration: fixture mà test mới import lại modify shared schema dùng chung với migration đang chờ deploy. Đáng ra là T1. Bài học là “luôn check paths cả khi label rõ ràng”. Quy tắc 30 giây ở trên ra đời sau lần đó.

Read budget: phân bổ thời gian đọc theo tier

Read budget phân bổ ngược tỉ lệ với tier number. T0 lấy 100% checklist Part 1 line-by-line; T3 chỉ sample. Đây là baseline tôi đang dùng, không phải số universal, hãy tune theo velocity của team bạn.

TierPhương pháp đọcThời gian baselineCó được skip mục nào?
T0Full checklist Part 1, no shortcut20–30 phút/PRKhông. Pair nếu khả thi.
T1Full checklist Part 110–15 phút/PRMục 6 (commit message) nếu test pass
T2Behavioral spot-check (xem phần dưới)5 phút/PRMục 3, 4, 5; giữ mục 1 + 2
T3Sample 1–2 chỗ + gate CI green2 phút/PRHầu hết, trừ check secret leak

Trade-off ở đây rõ ràng: T3 spot-check có thể miss bug nhỏ, đổi lấy việc T0 được đọc kỹ. Bạn không thoát trade-off này; chỉ chọn nó nằm ở đâu. Team không có cap sẽ phân bổ ngẫu nhiên, và bug T0 lọt qua nhiều hơn bug T3.

Điểm mấu chốt: Đừng dùng size diff làm proxy cho blast radius. PR 3 dòng đổi IAM nguy hiểm hơn PR 300 dòng thêm test. Size cho bạn biết bài đọc dài, không cho bạn biết nó nguy hiểm.

Câu hỏi hay tôi nhận hôm review draft này: “Tại sao không tune theo PR author thay vì tier?” Vì author signal noisy, một junior có thể merge T3 đúng còn một senior có thể đẩy T0 ẩu. Tier ghim vào artifact ổn định hơn ghim vào con người.

Behavioral spot-check cho T2/T3 chạy ra sao?

Behavioral spot-check là quan sát hành vi quan sát được (test chạy thật, snapshot, contract test) thay vì đọc diff dòng theo dòng. Khái niệm behavioral diff đã được định nghĩa ở Part 1; ở đây tôi chỉ áp protocol cụ thể cho T2/T3.

Protocol bốn bước, áp cho mọi PR T2/T3:

Bước 1: Pull branch xuống, chạy test suite.

Terminal window
gh pr checkout 4271
make test
# hoặc: npm test, pytest, cargo test
# Expected: tất cả test pass, kể cả test mới agent thêm.

What: chạy CI signal trên máy local. Why: tin máy của bạn, không tin Actions log một mình. Verify: exit code 0 và test mới appear trong output.

Bước 2: Behavioral diff cho một hàm/endpoint quan trọng.

Terminal window
# Lưu output hành vi với branch mới:
curl -s http://localhost:3000/api/checkout -d @sample.json > after.json
# Quay về main lưu output hành vi cũ:
git stash && git checkout main && make restart
curl -s http://localhost:3000/api/checkout -d @sample.json > before.json
# So sánh hành vi:
diff <(jq -S . before.json) <(jq -S . after.json)
# Expected (T2 hợp lệ): diff trống hoặc chỉ khác ở field mới có trong promise PR.
# Expected (red flag): khác ở field không liên quan tới PR scope.
git checkout - && git stash pop

What: so output trước và sau ở mức hành vi. Why: agent có thể đổi side-effect ngầm mà diff không cảnh báo. Verify: bạn đọc được diff JSON và giải thích được mọi dòng khác.

Bước 3: Spot-check một chỗ random trong diff bằng checklist Part 1 mục 2.

Chỉ một chỗ, không scan toàn bộ. Mục 2 (API dùng sai nhưng nhìn hợp lý) là mục agent fail nhiều nhất ở T2/T3, vì agent prefer API trông quen mắt hơn API đúng. Mở Files Changed, scroll random, đọc 20 dòng quanh đó, hỏi “API này có thật trong codebase không?”.

Terminal window
# Verify nhanh signature API thực sự tồn tại:
rg -n "fn process_payment" --type rust
# Expected: ít nhất một match ở src/, không chỉ ở test.

What: kiểm tra một sample API call. Why: bắt API fabrication, lỗi đặc trưng của agent. Verify: bạn confirm được symbol exist hoặc reject PR.

Bước 4: Gate quyết định.

Approve khi CI green AND behavioral diff không lạ AND spot-check pass. Bất kỳ failure nào ở ba điều kiện này: escalate tier (T2 → T1, T3 → T2), chạy full checklist Part 1, không thương lượng. Escalation là one-way ratchet: đã nâng tier thì không hạ lại trong cùng review.

Điểm mấu chốt: Behavioral spot-check đủ cho code path có test, không đủ cho error path không có test. Đây là trade-off bạn đã đồng ý khi chọn tier T2 thay vì T1.

Khi N PR AI/giờ: quy trình scale ra sao?

Tier-first triage scale tuyến tính vì triage chiếm dưới 2 phút. Bottleneck thật là T0, vẫn cần đọc serial. Khi N tăng, không phải scale review đều, mà là scale defer T3.

Ví dụ scenario tuần trước của tôi: 8 PR AI trong 1 giờ. Triage hết 2 phút, phân thành 1 T0 + 2 T1 + 3 T2 + 2 T3.

Inflow: [T0] [T1] [T1] [T2] [T2] [T2] [T3] [T3] (1 giờ)
| | | | | | | |
Outflow: now +30m +30m +2h +2h +2h EOD EOD
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
20m 10m 10m 5m 5m 5m 2m 2m (read budget)
= 59 phút tổng

T0 đọc trước, ngay. T1 batch 30 phút sau, hai PR liên tiếp dùng cùng context (cùng service). T2 chiều, sau cuộc họp lớn. T3 defer cuối ngày, có thể gộp vào weekly cleanup nếu trade-off đó OK với team.

Trade-off của defer: T3 defer quá lâu → backlog phình → dev mất niềm tin → họ bắt đầu open PR T3 fake là T2 để được review nhanh hơn. Cap T3 không defer quá 24 giờ; ngoài cap tự nâng T2 (Part 3 sẽ ship phần tự động này).

Cảnh báo: nếu một PR T0 có dependency lên một PR T3 (rare nhưng có), unblock T3 trước nhanh hơn defer. Tier không phải lý do block; tier là cách phân bổ.

FAQ

Tier label sai sau triage thì xử lý sao?

Nâng tier khi behavioral spot-check phát hiện scope rộng hơn dự đoán, chạy full checklist Part 1. Không hạ tier sau khi đã gắn, kể cả khi diff hoá ra ngắn (one-way ratchet, nghĩa là không bargain xuống).

Hai senior dev gắn tier khác nhau cho cùng một PR, ai đúng?

Default lên tier cao hơn, không tranh luận block review. Tranh luận async sau merge. Sau 5 lần bất đồng cùng kiểu, viết doc tiêu chí tier cho team, không sửa case-by-case.

Behavioral diff cho T2 có thực sự đủ thay cho line-by-line không?

Đủ cho code path có test, không đủ cho error path không có test. Đó là trade-off rõ ràng khi bạn chọn tier T2. Nếu PR T2 chạm error handling phức tạp, nó nên là T1, không phải T2 với behavioral diff.

Repo không có CODEOWNERS thì làm sao gắn tier?

Dùng path-based heuristic regex match: infra/|migrations/|auth/|secrets/ → T0; src/core/|src/lib/billing/ → T1; mọi thứ khác phân thẳng T2 và T3 theo có touch runtime hay không. Lập CODEOWNERS là tech-debt riêng, không block triage.

Có nên cho agent tự gắn tier không?

Không trong 2026. Agent label scope sai đủ thường để tier label trở thành noise, và một tier sai một lần đắt hơn 10 tier đúng. Part 3 sẽ tự động hoá bằng CI rule path-based, không phải bằng LLM.

Kết quả bạn có bây giờ

Bạn đã có một quy trình triage gắn tier trong dưới 30 giây, một bảng phân bổ read budget theo tier, và một protocol behavioral spot-check bốn bước cho T2/T3. Khi 3–4 PR AI cùng đáp xuống một giờ, bạn không còn chọn giữa “đọc kỹ tất cả” và “approve hết”. Bạn chọn đúng một PR để đọc kỹ, hai để spot-check, một để defer cap 24h.

Ba thứ có thể trật:

  1. Gắn nhầm tier vì tin agent label. Fix: luôn check paths trước khi tin label, re-tier khi spot-check phát hiện scope rộng hơn.
  2. T3 defer quá 24h. Fix: set cap cứng 24h, ngoài cap tự nâng T2.
  3. Team không đồng ý tiêu chí tier. Fix: default lên tier cao hơn, viết doc sau 5 case bất đồng.

Triage giữ tốc độ khi bạn còn ngồi ở bàn phím, nhưng đúng cái lúc bạn vào họp hai tiếng hoặc nghỉ phép, queue PR AI lại phình ra và những kiểm tra này vẫn cần phải chạy mà không có bạn.

Phần 3: Đẩy vòng review vào CI →