# Verification là code review kiểu mới

[Ray Amjad](https://homus.dev/speakers/ray-amjad) · [Ray Amjad](https://homus.dev/orgs/ray-amjad)

> Vì sao xây verifier (surface, answer key, so sánh) thay cho đọc từng dòng code của agent, kèm cách xây, bảo trì và audit verifier.

Topics: Coding Agents, Evals, Skills, Workflow

Canonical: https://homus.dev/talks/verification-is-the-new-code-review

## 1. Biểu đồ số thay đổi được merge mỗi ngày ở Anthropic

Ray mở đầu bằng một biểu đồ mà theo anh nhiều người đã thấy gần đây (video đăng ngày 01/11/2026): số thay đổi Anthropic merge vào code mỗi ngày. Theo Ray, ngày càng rõ là hầu như không ai ở Anthropic hay OpenAI còn đọc từng dòng code nữa. Vậy thì câu hỏi đặt ra là: các kỹ sư ở đó làm gì cả ngày?

![Biểu đồ Changes merged per day của monorepo Anthropic từ tháng 1 đến tháng 9 năm 2026, đỉnh 30,401 vào ngày 9/9](https://homus.dev/photos/JETl3kG_Ur0-0003.jpg)

Biểu đồ "Changes merged per day" (Anthropic monorepo, January–September 2026) trong bài viết của Anthropic mà Ray mở trong video đăng ngày 01/11/2026: cột tăng dần qua các tháng, có một "Quiet week" quanh đầu tháng 8, và nhãn đỉnh ghi 30,401 thay đổi được merge vào ngày 9/9.

Bài viết chứa biểu đồ này là "Redesigning source control at Anthropic". Theo bài viết (đọc ngày 01/11/2026), vào tháng 5/2026 Anthropic merge trung bình khoảng 2.500 thay đổi mỗi ngày, và tới tháng 9 thì hơn 30.000 mỗi ngày.

Theo Ray (video đăng ngày 01/11/2026), các kỹ sư Anthropic đã gợi ý câu trả lời qua nhiều bài blog, tập podcast và tweet khác nhau, nhưng anh thấy một cuộc phỏng vấn là một trong những ví dụ rõ ràng nhất anh tìm được. Cuộc phỏng vấn đó cho thấy điều các kỹ sư quan tâm nhiều nhất là plan và verification, thay vì đọc từng dòng code. Trong video này, anh muốn nói cụ thể việc đó trông như thế nào.

## 2. Viết lại messages API từ Python sang Rust bằng shadow traffic

Theo lời Ray kể lại tập podcast (video đăng ngày 01/11/2026), nhóm ở Anthropic đã viết lại messages API của Claude từ Python sang Rust, và nghe nói lúc bắt đầu, không ai trong nhóm là lập trình viên Rust. Claude viết code. Nhưng trước khi để Claude viết dòng nào, nhóm dừng lại và tự hỏi: verifier ở đây là gì? Làm sao biết bản mới đúng?

Theo Ray (video đăng ngày 01/11/2026), câu trả lời là shadow traffic. Bản mới bằng Rust và API cũ bằng Python nhận đúng cùng một lượng traffic thật. Nếu bản mới đúng, nó phải trả về đúng những câu trả lời giống hệt bản cũ. Ray hình dung cách nhóm làm là: mỗi lần bản Rust trả lời khác đi, phần diff đó được đưa lại cho Claude, Claude tìm ra chỗ sai, sửa, rồi nhóm chạy lại traffic. Khi hai bản khớp nhau, nhóm mới chuyển dần traffic thật sang.

![Sơ đồ vẽ tay: 1 the plan, Claude implementation, 2 the verification; humans viết plan, design the system và lo phần verification](https://homus.dev/photos/JETl3kG_Ur0-0143.jpg)

(1) "the plan" đi vào Claude ở phần "implementation", rồi tới (2) "the verification". Nhóm người ("humans") lo plan, "design the system" và phần verification.

Ray trích cách nhóm nói trong podcast (video đăng ngày 01/11/2026): hai điều quan trọng nhất là plan để làm việc đó, và verification của việc đó, vì phần implementation ở đây là Claude viết toàn bộ code. Claude viết code, còn con người dùng Claude để viết plan, design hệ thống và bảo đảm verifier đúng.

Theo Ray (video đăng ngày 01/11/2026), nhóm làm việc này vì giới hạn compute, và họ đã giảm được mức dùng CPU của API khoảng 40 đến 50%.

## 3. Đừng hỏi "test tốt hơn", hãy hỏi "verifier ở đây là gì"

Ray nói đây cũng là sự chuyển đổi tư duy anh đã hướng dẫn cho nhiều kỹ sư ở những công ty khá lớn trong vài tháng qua (tính đến video đăng ngày 01/11/2026). Thay vì nghĩ "làm sao để agent viết test tốt hơn?", người ta nghĩ "verifier cho việc này là gì?". Khi sắp làm một đợt refactor lớn hay implement một tính năng lớn: làm sao biết nó đúng? Cần verification gì ở đây? Có verification thì agent có thể hill climb dần tới câu trả lời đúng.

Theo Ray (video đăng ngày 01/11/2026), điều này quan trọng vì bây giờ khi nhờ agent viết test, nhất là unit test và integration test, các test đó về cơ bản nói lại những gì code đang làm. Thế là mọi test đều xanh, nhưng mở trang ra thì cái nút vẫn không làm gì cả.

Vấn đề Ray mô tả trong video đăng ngày 01/11/2026: test do agent viết nói lại những gì code đang làm, nên tất cả đều xanh, nhưng nút trên trang vẫn không làm gì.

Theo Ray (video đăng ngày 01/11/2026), đó là chỗ nhiều kỹ sư đang đánh một trận thua: họ cứ thêm luật vào file CLAUDE.md, kiểu "luôn viết test cho đàng hoàng", "không bao giờ làm yếu một assertion", mong agent bỗng dưng viết test tốt hơn. Nhưng nhìn vào những gì kỹ sư Anthropic đang làm, theo Ray, họ không nói về việc làm cho agent viết test tốt hơn, mà nói về việc tạo ra verification system, tooling và môi trường.

## 4. Verifier là gì: surface, answer key và phép so sánh

Ray muốn lùi lại một bước để mọi người hiểu cùng một nghĩa, bằng cách định nghĩa verifier. Theo định nghĩa của anh (video đăng ngày 01/11/2026), verifier về cơ bản là một phép kiểm tra mà agent không thể cãi lại được. Mọi verifier tốt đều có ba phần.

Phần thứ nhất là **surface**. Đó chính là bề mặt mà một người dùng thật, hoặc một hệ thống khác, sẽ chạm vào thay đổi mà coding agent của bạn đang làm. Theo Ray (video đăng ngày 01/11/2026), surface có thể là một trang web, một API, một MCP server, một CLI, một app mobile hay một ứng dụng desktop. Nếu bạn làm voice agent chẳng hạn, surface thậm chí có thể là qua điện thoại.

Phần thứ hai là thứ anh gọi là **answer key**: hình dạng của "đúng" với hệ thống của bạn. Điều quan trọng nhất ở đây, theo Ray (video đăng ngày 01/11/2026), là agent không viết ra nó: bạn định nghĩa nó từ trước, với sự giúp đỡ của agent.

Phần thứ ba, theo định nghĩa của Ray (video đăng ngày 01/11/2026), là **phép so sánh**: những gì đã xảy ra có khớp với answer key hay không?

Ba phần của một verifier tốt theo định nghĩa Ray đưa ra trong video đăng ngày 01/11/2026: surface, answer key và phép so sánh.

Theo Ray (video đăng ngày 01/11/2026), đây là khác biệt lớn so với việc nhờ agent viết test. Khi agent viết test, nó viết luôn cả answer key, nên tất nhiên test sẽ pass. Còn ở đây, bạn phải định nghĩa answer key từ trước.

## 5. Xếp hạng các loại answer key

Theo Ray (video đăng ngày 01/11/2026), một verifier tốt đến đâu là do answer key của nó quyết định. Anh thấy answer key tốt nhất là một hệ thống cũ đã có sẵn và làm cùng một việc. Với ví dụ messages API của Anthropic ở trên, answer key chính là hệ thống Python cũ.

Một loại answer key khác, theo Ray (video đăng ngày 01/11/2026), là snapshot. Answer key cũng có thể là một test suite độc lập, không phụ thuộc vào chính đoạn code đó, chẳng hạn các end-to-end test viết bằng Playwright. Và ở cuối danh sách là spec: spec cũng có thể làm answer key.

![Bảng xếp hạng answer key từ best tới worst: old system, snapshot, independent test suite, spec](https://homus.dev/photos/JETl3kG_Ur0-0623.jpg)

Thang xếp hạng answer key Ray dùng trong video đăng ngày 01/11/2026, từ "best" xuống "worst": (1) old system, ghi "exact"; (2) snapshot, ghi "screenshot"; (3) independent test suite, ghi "not yours"; (4) spec, ghi "agent judges". Dưới cùng, sát chữ "worst", là một dòng trống.

## 6. Spec làm answer key: bảng user action và expected result

Ray kể cách anh làm trong các spec của mình (video đăng ngày 01/11/2026): answer key là một bảng đặt ở cuối spec, ghi người dùng phải làm được gì và kết quả mong đợi là gì.

Ví dụ của anh (video đăng ngày 01/11/2026): có một app ghi chú rất đơn giản, và anh viết spec để thêm tính năng gắn tag. Bảng sẽ có những dòng kiểu:

- User action: người dùng thêm được một tag vào một ghi chú. Expected result: tag hiện lên trên ghi chú ngay lập tức.

- User action: người dùng lọc ghi chú theo tag. Expected result: chỉ những ghi chú có tag đó hiện ra.

- User action: xoá hẳn một tag. Expected result: tag biến mất khỏi mọi ghi chú và khỏi danh sách tag.

Theo Ray (video đăng ngày 01/11/2026), anh có thể định nghĩa khoảng 10 hay 15 dòng như vậy, tuỳ hệ thống tag anh muốn phức tạp tới đâu, và chúng tạo thành answer key của anh.

Theo Ray (video đăng ngày 01/11/2026), tất nhiên loại answer key này hơi yếu hơn, vì ta phải dựa vào một agent để đưa ra phán quyết cuối cùng. Lý tưởng là có một agent khác quyết định xem expected result đã đạt hay chưa.

## 7. Framework e2e: một agent làm, một agent khác chấm

Một framework mã nguồn mở khá hay mà Ray thích cho việc này tên là e2e (video đăng ngày 01/11/2026). Anh mở trang của nó và chỉ ra cách định nghĩa một end-to-end test.

![Trang e2e.tester.army với file checkout.e2e.ts: test a member upgrades to Pro](https://homus.dev/photos/JETl3kG_Ur0-0523.jpg)

File ví dụ `checkout.e2e.ts` trên trang của framework e2e mà Ray mở trong video đăng ngày 01/11/2026, dòng `agent.assert` đang được bôi chọn.

Nội dung file ví dụ trên trang đó, như trong video đăng ngày 01/11/2026:

```
import { test, expect } from 'e2e';

test('a member upgrades to Pro', async ({ app, agent, screen }) => {
await app.open('/settings/billing');

await agent.act('upgrade the workspace to the Pro plan');
await agent.assert('the invoice preview shows a prorated amount');

await expect(screen.getByRole('status')).toContainText('Pro');
});
```

Ray đọc ví dụ này như sau (video đăng ngày 01/11/2026): một thành viên nâng cấp lên Pro. Ta mở trang billing, rồi một agent dùng browser để "act": nâng cấp workspace lên gói Pro. Sau đó một agent khác, có thể dùng một model khác, chẳng hạn một model "system one" nhanh và rẻ, quyết định xem invoice preview có hiện số tiền tính theo tỷ lệ (prorated) hay không. Rồi ta có thể thêm vào một phần deterministic.

Theo Ray (video đăng ngày 01/11/2026), anh rất thích framework này vì nó cho phép kết hợp kiểm thử deterministic và agentic với nhau. Ray chỉ vào hai dòng trên trang và nói chúng cho thấy framework cùng quan điểm với anh: agent quyết định expected result đã đạt hay chưa là một agent khác với agent thực hiện các thao tác.

Theo Ray (video đăng ngày 01/11/2026), framework này còn có những thứ như caching, để bạn chỉ trả tiền token khi một test fail. Ở một số project của mình, anh đã xoá hết mọi test cũ: anh cho xem một PR xoá 110.000 dòng, một PR khác xoá 295.000 dòng, rồi thay tất cả bằng e2e test. Theo số đo của anh (video đăng ngày 01/11/2026), nhìn chung anh thấy với cách này anh còn bắt được nhiều bug hơn.

Ray tả cách anh làm (video đăng ngày 01/11/2026): mỗi khi một spec đã được implement xong, mọi hành vi người dùng định nghĩa ở cuối spec được biến thành một e2e test, để bảo đảm những hành vi đó được giữ nguyên về sau. Đó là verifier của anh: answer key lấy từ spec, rồi được định nghĩa trong code giống như ví dụ trên.

Và theo Ray (video đăng ngày 01/11/2026), tình huống tệ nhất là không có answer key nào cả, vì khi đó agent tự quyết định "verify" trông như thế nào. Kết quả có thể là unit test xanh hết mà nút vẫn hỏng.

## 8. Deterministic, agentic, hay cả hai; refactor hay tính năng mới

Như đã thấy, theo Ray (video đăng ngày 01/11/2026), có những verifier hoàn toàn deterministic và chỉ dựa vào script, như phép diff response của API ở đầu video. Có những verifier dựa vào phán đoán của agent, như khi implement một spec. Và có những verifier là một chút của cả hai.

![Sơ đồ deterministic, agent's judgment, a bit of both; câu hỏi refactor or new feature dẫn tới old system or snapshot hoặc agentic end-to-end test](https://homus.dev/photos/JETl3kG_Ur0-0723.jpg)

Hàng trên là ba loại verifier ("deterministic" với "API response diff", "agent's judgment" với "implementing a spec", và "a bit of both"); hàng dưới là câu hỏi "refactor or new feature?", nhánh refactor dẫn tới "old system or snapshot", nhánh new feature dẫn tới "agentic end-to-end test".

Cách chia lớn của riêng Ray (video đăng ngày 01/11/2026) là hỏi: đây là một đợt refactor, khi đó ta có thể dùng hệ thống cũ hoặc một snapshot làm verifier? Hay đây là một tính năng mới, khi đó có thể ta muốn dùng một agentic end-to-end test?

## 9. Ba việc với verifier và skill create-prove-it

Theo Ray (video đăng ngày 01/11/2026), ngày nay, giống nhiều kỹ sư ở Anthropic và OpenAI, phần lớn thời gian trong ngày của anh dành cho việc xây và bảo trì verifier tốt. Mọi thứ quy về ba việc: xây verifier tốt; bảo trì chúng để chúng không bị cũ; và audit chúng để chắc chắn chúng thật sự bắt được lỗi.

Anh gói cách làm này thành ba skill cho Claude Code và Codex (video đăng ngày 01/11/2026): `create-prove-it`, `maintain-prove-it` và `audit-prove-it`.

Theo Ray (video đăng ngày 01/11/2026), `create-prove-it` tạo ra một skill khác, gọi là một skill prove-it. Thứ tự nó làm việc: nó map hết các surface; rồi kiểm tra xem có thật sự chạm tới được các surface đó không; rồi tìm những thứ có thể dùng làm answer key và brainstorm cùng bạn; rồi sinh ra một skill. Sau khi bạn đã implement xong, bạn chạy skill đó thật.

![Sơ đồ create-prove-it: 1 maps all the surfaces, 2 reach the surfaces, 3 các bảng, rồi tới bước 4 và 5](https://homus.dev/photos/JETl3kG_Ur0-0803.jpg)

Sơ đồ `create-prove-it` trong video đăng ngày 01/11/2026, lúc đang hiện dần: bước 1 "maps all the surfaces", bước 2 "reach the surfaces" với ba ô có dấu tích, bước 3 là các bảng; bước 4 và 5 mới chỉ có số.

## 10. Demo: tìm cách chứng minh một MCP server mới chạy đúng

Ray làm một ví dụ nhanh: giả sử anh muốn thêm một MCP server vào một repo dịch vụ sandbox (demo trong video đăng ngày 01/11/2026). Anh gõ `/create-prove-it` trong Claude Code. Theo lời anh, đây sẽ là skill luôn được dùng để verify và chứng minh mọi thay đổi xảy ra với MCP server đó. Anh nhờ nó, đại ý: anh muốn làm một MCP server hoàn toàn mới cho repo này, nó có thể giúp anh brainstorm xem sẽ verify, sẽ "prove it" thế nào khi làm xong không, rồi bấm Enter.

Theo Ray (video đăng ngày 01/11/2026), việc bạn đang làm là kích hoạt skill để tạo một skill prove-it cho bất kỳ surface nào trong ứng dụng, nhưng bạn cũng dùng được nó cho các đợt refactor lớn. Chẳng hạn anh gõ `/create-prove-it` rồi nói anh muốn chuyển backend API từ Go sang Rust, và nó sẽ đi tìm cách để anh chứng minh việc đó đã xong.

Kết quả cho MCP server quay về (demo trong video đăng ngày 01/11/2026): theo lời Ray đọc, surface sẽ là MCP server, đang ở trạng thái "planned". Các surface đang có là public API, dashboard UI, dữ liệu đã lưu (stored state) và log. Rồi nó đưa ra một loạt cách chứng minh, mỗi cách kèm phương pháp, answer key và chi phí.

![Bảng Prove-it skill for MCP server trong terminal: sáu phương pháp với cột Answer key, Catches, Misses, Cost](https://homus.dev/photos/JETl3kG_Ur0-0943.jpg)

Bảng "Prove-it skill for MCP server" trong terminal (demo trong video đăng ngày 01/11/2026), gồm các cột Method, Answer key, Catches, Misses, Cost (setup / per run). Sáu phương pháp: MCP vs API parity script, lifecycle round trip, break-it probes, một agent thật điều khiển MCP, dashboard check bằng Playwright, và log scan sau mỗi lần chạy.

Trong bảng đó (demo trong video đăng ngày 01/11/2026), ví dụ phương pháp 1 là một script so MCP với API (cùng key, cùng thời điểm), answer key là API, bắt được các trường bị rơi khỏi một dòng sandbox, phân trang sai, hay lỗi 404 bị biến thành lỗi chung chung; nó bỏ sót những gì cả hai cùng sai, chi phí thấp, khoảng một phút. Phương pháp 4 cho một agent thật (Claude Code nối vào MCP, được giao một việc) điều khiển MCP, answer key là spec, bắt được tên tool hay mô tả không rõ và những thông báo lỗi mà agent không xử lý được; chi phí ở mức trung bình, tốn token, kết quả mỗi lần một khác.

Ray chọn kết hợp các phương pháp 1, 2, 3 và 6 (demo trong video đăng ngày 01/11/2026). Giờ anh có thể biến chúng thành một skill để dùng khi MCP server đã được implement, nhằm verify rằng mọi thứ chạy đúng. Nếu có gì fail, việc đó quay lại builder: builder agent sửa, rồi verifier thử verify lại flow.

Vòng lặp Ray tả trong video đăng ngày 01/11/2026: verifier kiểm MCP server, cái gì fail thì quay lại builder agent để sửa, rồi verifier verify lại flow.

Sau đó, theo Ray (video đăng ngày 01/11/2026), anh sẽ có một skill prove-it riêng cho MCP server này. Mỗi khi MCP server thay đổi trong tương lai, skill đó sẽ tự được dùng để verify rằng thay đổi đã được làm đúng.

## 11. Demo: chuyển backend API từ Go sang Rust

Tương tự, với việc chuyển từ Go sang Rust (demo trong video đăng ngày 01/11/2026), skill cho Ray xem một loạt phương pháp anh có thể chọn, mỗi cách bắt được gì, và chi phí setup là bao nhiêu. Bảng "Go-to-Rust API prove-it skill" gồm: differential replay (gửi cùng request cho Go và Rust rồi diff status, body và lỗi), state parity (tạo, pause, resume, kill qua từng API rồi diff các dòng Postgres và danh sách sandbox), chạy integration suite với bản Rust, read-only shadow (mirror các GET thật sang Rust và log diff), và gRPC parity cho dịch vụ routing.

Ví dụ read-only shadow, một shadow API (demo trong video đăng ngày 01/11/2026): theo Ray, cách này có chi phí setup khá cao vì về cơ bản nó chạy liên tục, nhưng nó cũng cho hiệu quả tốt nhất, theo nghĩa nó dùng những dạng traffic mà không ai nghĩ tới việc viết script.

Vòng lặp cho một refactor lớn mà Ray tả trong video đăng ngày 01/11/2026: giữ hệ thống cũ chạy làm answer key, chép request sang hệ thống mới, so mọi response, đưa diff cho Claude tìm root cause và sửa, tới khi không còn khác biệt.

Theo Ray (video đăng ngày 01/11/2026), với một refactor lớn, việc đầu tiên cần xác định là answer key của bạn là gì. Ở ví dụ Anthropic, answer key là API cũ. Bạn giữ hệ thống cũ chạy, chép mọi request sang hệ thống mới, so từng response, rồi đưa mọi diff, mọi chỗ không khớp, lại cho Claude. Claude tìm root cause, sửa, và bạn tiếp tục cho tới khi không còn khác biệt nào.

## 12. Bun: test suite viết bằng TypeScript làm answer key

Theo Ray (video đăng ngày 01/11/2026), Bun cũng làm một việc rất giống thế khi viết lại runtime từ Zig sang Rust, nhưng answer key của nhóm Bun là test suite đã có sẵn. Anh mở bài viết của Bun và cuộn xuống đoạn có câu, đại ý: "May mắn là test suite của chính Bun được viết bằng TypeScript, nghĩa là nó không phụ thuộc vào ngôn ngữ lập trình của runtime."

Câu đó trong bài "Rewriting Bun in Rust" (đọc ngày 01/11/2026) nguyên văn là: "Fortunately, Bun's own test suite is written in TypeScript which means it doesn't depend on the runtime's programming language."

Theo lời Ray (video đăng ngày 01/11/2026), sau khi nhóm Bun cho Claude dùng cả một loạt sub-agent trong một workflow động để làm việc chuyển đổi, nhóm hill climb lại được tới 100% test pass, không test nào bị bỏ qua hay bị xoá. Bài viết của Bun (đọc ngày 01/11/2026) ghi rằng bản viết lại được merge khi 100% test suite của Bun pass trong CI trên mọi nền tảng, và tác giả đã tự kiểm tra là các test thật sự chạy chứ không bị bỏ qua.

## 13. Byte-for-byte: Crimsonland và Ghostty

Một verifier thú vị khác mà Ray nêu (video đăng ngày 01/11/2026) là so sánh byte-for-byte. Theo anh, gần đây có một trào lưu thú vị: người ta dùng agent để làm matching decompilation các game cũ. Nghĩa là để agent dựng lại source code của game từ file binary, rồi kiểm tra xem nó có compile ngược lại ra đúng y hệt machine code của game gốc hay không.

Ví dụ Ray đưa ra (video đăng ngày 01/11/2026): có người cho agent chạy vài trăm giờ để làm một matching decompilation của game Crimsonland, rồi đăng lên GitHub. Theo Ray, anh có thể mở link đó trong browser và chơi luôn nếu muốn. Repo này (đọc ngày 01/11/2026) mô tả nó là một matching decompilation: source C/C++ mà Visual C++ 6 compile ngược ra đúng file `crimsonland.exe` và `grim.dll` gốc, từng lệnh một, và bản compile sang WebAssembly chơi được game gốc ngay trong browser.

![Sơ đồ byte-for-byte: original game ra dãy byte làm answer key, agent's code qua same old compiler ra dãy byte so sánh, verifier đánh dấu khớp](https://homus.dev/photos/JETl3kG_Ur0-1223.jpg)

Dãy byte của "original game" là answer key; "agent's code" đi qua "same old compiler" ra dãy byte thứ hai; verifier so từng byte xem có bằng nhau không.

Theo Ray (video đăng ngày 01/11/2026), các dự án này dùng cùng một ý tưởng verifier. Ở đây, answer key là game gốc; bạn compile code của agent bằng đúng trình compile cũ; và phép so sánh là các byte: chúng có khớp hoàn toàn hay không.

Một ví dụ byte-for-byte hay khác mà Ray nêu (video đăng ngày 01/11/2026) là của Mitchell Hashimoto, người làm ra terminal Ghostty. Anh ấy muốn Ghostty hiển thị hình ảnh theo đúng cách Kitty làm. Kitty, một terminal Ray thích dùng, có một cách để hiện hình ảnh bên trong terminal, và nhiều chương trình hiện dùng cách đó (tính đến video đăng ngày 01/11/2026). Mitchell muốn chính những chương trình đó chạy được trên Ghostty. Nên trong trường hợp của anh ấy, answer key về cơ bản là Kitty.

## 14. Pixel diff: screenshot làm answer key

Một ví dụ liên quan khác Ray gợi ý (video đăng ngày 01/11/2026) là pixel diff, khi đó một screenshot có thể làm answer key. Anh lấy trang fingerprint.com, một trang anh rất thích thiết kế, đưa cho agent và bảo: dùng một builder verifier loop để chép lại thiết kế của trang web này, lấy pixel diff làm verifier.

Anh cho xem nó chạy (demo trong video đăng ngày 01/11/2026): bản gốc ở bên trái, bản của anh ở bên phải, và ảnh pixel diff. Cuộn xuống, theo lời anh đọc, diff đi từ 19% xuống 1,8%, rồi 0,54%, rồi 0,52%, rồi dừng. Và nó ghi rằng nếu bỏ phần logo ra thì diff thực ra chỉ còn 0,07%.

![Thread Slack của agent Percy: Round 3 0.52 phần trăm với ba ảnh reference, build, diff và phần How the loop works](https://homus.dev/photos/JETl3kG_Ur0-1323.jpg)

Thread trên Slack trong demo của video đăng ngày 01/11/2026: agent báo "Round 3" với diff 0.52% với ba ảnh reference, build và diff, rồi ghi trang chưa đạt mục tiêu 0.1%, chuỗi diff 19.07% → 1.89% → 0.54% → 0.52%, và 0.07% khi che map tile, logo khách hàng và icon thương hiệu. Phần "How the loop works" ghi builder là một Sonnet subagent viết HTML/CSS thuần, còn verifier chạy pixelmatch (threshold 0.1) so với ảnh reference.

Theo Ray (video đăng ngày 01/11/2026), tất nhiên đây là một ví dụ dễ hiểu, nhưng có người đã làm đúng việc này khi thu nhỏ các file font trên website để trang tải nhanh hơn: họ dùng pixel diff làm verifier.

## 15. Tính năng mới: spec là answer key, và một ví dụ thật trên Slack

Theo Ray (video đăng ngày 01/11/2026), mọi thứ anh nói tới giờ đều cần có một loại answer key nào đó, nhưng với một tính năng hoàn toàn mới thì thường không có hệ thống cũ nào để dựa vào. Khi đó answer key là chính spec của bạn. Khi viết spec cùng agent, bạn muốn định nghĩa hành vi mong đợi với người dùng. Quay lại ví dụ trước: nếu thêm tag vào ghi chú thì tag phải thật sự hiện ra. Điều chính, theo Ray, là mọi hành vi, mọi con đường người dùng có thể đi trong ứng dụng, phải được định nghĩa ngay ở giai đoạn viết spec. Và như anh nói với thư viện e2e, sau khi spec được implement, bạn có thể biến chúng thành end-to-end test.

Để cho xem một ví dụ thật với một ứng dụng thật, Ray mở Slack. Anh nói dù phần lớn video của anh làm trong Claude Code trên terminal, khoảng 97% việc code của anh diễn ra trong sandbox, qua Slack (tính đến video đăng ngày 01/11/2026). Anh cho rằng Slack là surface tốt nhất để build cùng agent, nhất là với tính năng code channels mới (video đăng ngày 01/11/2026).

Trong Slack (demo trong video đăng ngày 01/11/2026), anh xem được toàn bộ code agent đã viết, và cả video quay lại mọi thứ đã được verify. Mở một bản ghi, anh thấy agent đang verify và test một ứng dụng macOS, để chắc rằng tính năng chọn micro (mic picker) mới mà nó thêm vào thật sự chạy. Trong trường hợp này agent verify cả một loạt user flow. Ở cuối, theo lời Ray đọc, nó ghi rằng trên một máy Windows thật, nó pass bốn trong năm flow.

![Sơ đồ builder verifier loop trên một real Windows machine: năm flow có dấu tích nối với answer key, chữ implemented correctly](https://homus.dev/photos/JETl3kG_Ur0-1523.jpg)

Verifier và builder nối thành "builder verifier loop", chạy các flow trên một "real Windows machine", mỗi flow đối chiếu với answer key, với chữ "implemented correctly" ở dưới.

Theo Ray (video đăng ngày 01/11/2026), anh nghĩ sau đó agent quay lại builder để sửa phần bị hỏng, và cứ qua lại trong builder verifier loop như vậy để chắc rằng mọi thứ đã được implement đúng và chạy đúng như anh định nghĩa trước đó trong answer key. Với một setup kiểu này, theo anh, anh giao được những kết quả rất đáng tin cậy.

## 16. Việc thứ hai: maintain prove-it, bài học từ Safari

Việc thứ hai là bảo trì các skill prove-it, vì theo Ray (video đăng ngày 01/11/2026), chúng có thể bị cũ theo thời gian. Anh kể một ví dụ thật: trên một trang web có trình phát video của anh, một số người gặp vấn đề là phát video đôi khi hỏng trên Safari. Anh đã sửa trình phát video trên website, và nó chạy tốt trên Chrome nhưng lại hỏng trên Safari, đôi khi cả Firefox.

Lý do lỗi lọt qua, theo Ray (video đăng ngày 01/11/2026): skill prove-it của anh bấm quanh website và test thay đổi trong Playwright Chromium, nhưng Safari và Chrome xử lý phát video hơi khác nhau, nên thay đổi đó thực ra chưa hề được verify trên Safari.

![Sơ đồ: user báo Safari playback, my agent dùng Playwright WebKit dựa trên Safari và một real Mac](https://homus.dev/photos/JETl3kG_Ur0-1623.jpg)

Người dùng báo lỗi "Safari playback" ở phần "video playback"; "my agent" được nối tới "Playwright WebKit" ("based on Safari") và một "real Mac".

Khi một người dùng báo với anh là phát video trên Safari không ổn, Ray kể (video đăng ngày 01/11/2026), anh bảo agent dựng một Playwright WebKit, vốn dựa trên Safari, và dùng thêm một máy Mac thật mà agent có quyền truy cập, để tìm ra vấn đề và sửa. Sửa xong, anh muốn chắc rằng các thay đổi sau này không làm hỏng Safari lần nữa, nên bản sửa đó được đưa ngược lên (upstream) vào skill prove-it. Anh bảo agent: mọi thứ chạm tới phần phát video, trong skill prove-it, cũng phải được kiểm bằng Playwright WebKit và trên Safari thật ở một chiếc MacBook mà agent có quyền truy cập.

Lùi lại nhìn tổng thể, theo Ray (video đăng ngày 01/11/2026), meta pattern là: dù skill prove-it và các skill verification sẽ bắt được nhiều vấn đề trước khi chúng lên production, vẫn sẽ có vấn đề người dùng gặp phải và báo lại. Thay vì nghĩ "lẽ ra phải prompt agent tốt hơn thế nào?" hay "lẽ ra phải viết spec tốt hơn thế nào?", thì đúng hơn là chuyện: làm sao cải thiện skill prove-it ở upstream, để agent bắt được những lỗi đó trước khi chúng tới production?

Theo Ray (video đăng ngày 01/11/2026), đó chính là việc skill `maintain-prove-it` giúp bạn làm. Sau vài lần chạy, bạn chạy nó, và nó xem lại các lần chạy trước. Nó cũng xem qua feedback của người dùng và các bug đã được báo, rồi tìm những cải tiến có thể làm cho skill prove-it. Nhờ vậy, khi một bug lọt qua và bị người dùng báo hoặc hiện lên trong Sentry, cả một lớp bug và vấn đề tương tự có thể được xử lý trước khi tới tay người dùng trong tương lai.

## 17. Việc thứ ba: audit prove-it bằng mutation testing

Việc thứ ba là audit các skill prove-it đều đặn, để chắc rằng chúng thật sự bắt được vấn đề. Theo Ray (video đăng ngày 01/11/2026), một trong những cách tốt nhất để làm việc này là mutation testing: cố tình làm hỏng app để xem các phép kiểm có phát hiện ra không. Đó là việc skill `audit-prove-it` làm.

![Sơ đồ audit-prove-it: realistic bug cài vào a recent change, count +1 off by 1, fresh agent verify the flow adding tags to notes](https://homus.dev/photos/JETl3kG_Ur0-1803.jpg)

Sơ đồ `audit-prove-it` trong video đăng ngày 01/11/2026: một "realistic bug" được cài vào "a recent change" (ví dụ "count +1", "off by 1"), rồi một "fresh agent" được giao "verify the flow" cho việc "adding tags to notes".

Theo Ray (video đăng ngày 01/11/2026), skill này lấy một thay đổi bạn làm gần đây, cài một bug thực tế vào code, chẳng hạn làm bộ đếm lệch một đơn vị (off by one). Rồi một agent mới tinh được bảo verify lại flow, ví dụ quay lại chuyện thêm tag vào ghi chú. Skill xem agent mới đó có tìm ra vấn đề không, mà không được ai nói cho biết cái gì đã hỏng. Nếu bạn dùng verifier agentic thay vì deterministic, và nó bắt được bug, thì rất tốt. Nếu không, nghĩa là skill của bạn cần sửa lại.

## 18. Kết: khi không ai còn đọc từng dòng code

Quay lại điểm xuất phát của video, theo Ray (video đăng ngày 01/11/2026): có quá nhiều dòng code đang được viết và quá nhiều thay đổi đang được merge, tới mức không ai còn thật sự đọc từng dòng code nữa. Tuỳ ngành bạn làm, điều đó có thể ổn, miễn là có thứ gì khác kiểm tra nó. Lý tưởng thì thứ đó là một verifier nào đó, và trước khi làm một thay đổi lớn, bạn nên hỏi agent: verifier ở đây chính xác là gì? Làm sao ta biết nó đúng khi đã implement xong?

Theo Ray (video đăng ngày 01/11/2026), như đã thấy từ tập podcast, nhiều kỹ sư giỏi nhất đang tập trung xây verification tooling quanh agent của mình. Với lượng chú ý có hạn mỗi ngày, họ bảo đảm agent đã tự đi qua đủ verification trước khi đưa một thay đổi về cho họ. Và thường thì họ thậm chí không đọc code của thay đổi đó: họ xem video, nhìn hình ảnh đồ hoạ và các file HTML.

## Nguồn và liên kết

- Tên gốc: Verification is the new code review
- Sự kiện: [YouTube](https://homus.dev/events/youtube)
- Video gốc: [https://www.youtube.com/watch?v=JETl3kG_Ur0](https://www.youtube.com/watch?v=JETl3kG_Ur0)

- Kênh YouTube Ray Amjad (Ray Amjad): [youtube.com/channel/UCLA7cJBnqr0nLF2bQBD9uUg](https://www.youtube.com/channel/UCLA7cJBnqr0nLF2bQBD9uUg)

- Anthropic, "Redesigning source control at Anthropic", bài có biểu đồ Changes merged per day và số liệu khoảng 2.500 thay đổi mỗi ngày vào tháng 5/2026, hơn 30.000 mỗi ngày vào tháng 9 (đọc ngày 01/11/2026): [anthropic.com/research/redesigning-source-control](https://anthropic.com/research/redesigning-source-control)

- e2e, framework kiểm thử AI mã nguồn mở viết bằng TypeScript, trang có file ví dụ `checkout.e2e.ts` (đọc ngày 01/11/2026): [e2e.tester.army](https://e2e.tester.army)

- Bun, "Rewriting Bun in Rust", có đoạn về test suite viết bằng TypeScript và điều kiện 100% test pass trước khi merge (đọc ngày 01/11/2026): [bun.com/blog/bun-in-rust](https://bun.com/blog/bun-in-rust)

- Repo Crimsonland, matching decompilation của game Crimsonland (đọc ngày 01/11/2026): [github.com/banteg/crimson](https://github.com/banteg/crimson)

- Ghostty, terminal của Mitchell Hashimoto: [ghostty.org](https://ghostty.org) (đọc ngày 01/11/2026)

- Tài liệu giao thức hiển thị hình ảnh của terminal Kitty (đọc ngày 01/11/2026): [sw.kovidgoyal.net/kitty/graphics-protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/)

Nguồn: https://homus.dev/talks/verification-is-the-new-code-review · cập nhật 2026-11-01
