TL;DR Muse thay thế các bảng điều khiển điều phối agent được xây dựng riêng bằng bốn công cụ mà mọi đội ngũ phần mềm đã có sẵn: Slack, Linear, GitHub và Claude Code. Không dashboard, không SPA, không một dòng mã nào do con người viết. Mỗi PR đều phải vượt qua bảy cổng CI và một hội đồng đánh giá AI ba vai trò, theo chính sách merge không dung thứ bất kỳ phát hiện nào. 40,000 lượt bình chọn đã được thu thập, việc đánh giá mô hình nền tảng nhanh hơn 80%.
Muse, viết tắt của Meshy Universal System for Evaluation, là nền tảng đánh giá mô hình nội bộ của chúng tôi. Các đội ngũ tại Meshy sử dụng nó hàng ngày để xác định xem các checkpoint mô hình mới có hoạt động tốt hơn các checkpoint cũ hay không.
Một ngày nọ, một checkpoint mô hình mới đầy hứa hẹn vừa hoàn thành quá trình huấn luyện. Nó cần một trận đấu đối kháng để xác định chiến lược huấn luyện tiếp theo. Đội ngũ nhận ra họ cần một tính năng mới trong Muse để hỗ trợ việc này.
"@Linear tạo một issue Muse về việc này"
Quản lý sản phẩm nói vậy trên Slack.
Một phút sau, issue Linear MES-12345 được tạo ra.
Chủ sở hữu nền tảng Muse — chính là tôi — nhìn thấy issue này. Nó cần được triển khai càng sớm càng tốt (ASAP) để gỡ vướng cho tác vụ đánh giá. Sau đó, tin nhắn được gửi tới Claude:
"Điều tra và sửa MES-12345"
Claude xem xét issue, rồi đưa ra hai quyết định thiết kế cần cân nhắc. Các quyết định được đưa ra, và tôi rời khỏi terminal để làm việc khác.
Một tiếng rưỡi sau, một phiên bản mới của Muse được triển khai. Xong.
Kỹ thuật phần mềm tự động không nhất thiết phải là những bảng điều khiển điều phối agent hay dashboard hào nhoáng. Và trong bài viết này, tôi sẽ giải thích cách chúng tôi biến điều đó thành hiện thực.
Một gallery, một trình xem, và một nút bình chọn
Câu hỏi mà Muse trả lời trông có vẻ đơn giản nhưng thực ra rất khó: liệu checkpoint mô hình mới có thực sự tốt hơn không? Tất nhiên, mỗi lần huấn luyện đều có các chỉ số định lượng để theo dõi, nhưng chúng còn lâu mới phản ánh chính xác thế nào là "tốt" trong việc tạo sinh 3D. Vì vậy, Muse làm một việc đơn giản nhưng hiệu quả: kiểm thử mù đôi (double-blind testing). Nó đặt kết quả đầu ra của hai mô hình cạnh nhau, cùng một ảnh đầu vào, vị trí trái phải được xáo trộn ngẫu nhiên, và để các đồng nghiệp bình chọn mà không biết mô hình nào là mô hình nào.
Một lần đánh giá là một loạt các cặp như vậy. Ai đó chỉ cho Muse hai bộ mesh được tạo ra và một thư mục ảnh tham chiếu, và ứng dụng sẽ tự lo phần còn lại: ghép cặp các mô hình, làm mù việc gán cặp, và hiển thị từng cặp so sánh một lúc một. Lượt bình chọn tích lũy dần bất cứ khi nào mọi người có vài phút rảnh. Toàn bộ giao diện được thiết kế để dùng nhanh gọn trong thời gian ngắn, và gần như có thể điều khiển hoàn toàn bằng phím tắt. Một người bình chọn phải với tay lấy chuột sẽ bình chọn ít hơn.
![]()
Giao diện bình chọn của Muse: hai mesh được tạo ra, cùng một ảnh đầu vào, vị trí trái phải được xáo trộn ngẫu nhiên.
Đó chính là sản phẩm. Một gallery, một trình xem, và một nút bình chọn. Không có gì trong chức năng của Muse là phức tạp cả, và đó chính xác là lý do nó trở thành một case study rõ ràng. Điều bất thường duy nhất nằm ở cách nó được xây dựng.
Một kiến trúc mà agent có thể điều hướng được
Dự án Muse bắt đầu vào cuối năm 2025, khi "vibe coding" vừa trở thành một từ thông dụng mới. Bên cạnh việc phục vụ nhu cầu đánh giá ngày càng tăng của Meshy trong các chu kỳ huấn luyện và phát hành, Muse còn có một mục tiêu đầy tham vọng: xây dựng phần mềm cho toàn bộ công ty mà không cần một dòng mã nào do con người viết.
Ngày nay, những kết quả đạt được thực sự xuất sắc:
- Hơn 40,000 lượt bình chọn đã được thu thập kể từ khi Muse ra mắt lần đầu, với hơn 30 dự án đánh giá mỗi tháng
- Hơn 4 đội ngũ đang tích cực sử dụng Muse cho công việc hàng ngày của họ
- Codebase nội bộ đầu tiên có ít nhất 1 thành viên không thuộc mảng kỹ thuật chủ động đóng góp
- Codebase nội bộ đầu tiên đạt được khả năng phát triển hoàn toàn tự động
Tính tổng thể, chúng tôi ước tính rằng Muse đã giúp việc đánh giá mô hình nền tảng của chúng tôi nhanh hơn ít nhất 80% và cắt giảm ít nhất 10 person-day công sức trong mỗi lần phát hành mô hình. Nếu không có Muse, việc duy trì tốc độ phát triển và phát hành hiện tại cho bộ sưu tập mô hình sinh của Meshy như hình học, texture, và smart topology, gần như là bất khả thi.
Ngay từ ngày đầu tiên, Muse đã được thiết kế để agent có thể đọc hiểu (agent-legible) tối đa. Vào năm 2025, chúng tôi đã nhận thấy rằng khi triển khai cùng một use case, agent gặp khó khăn ở một số khía cạnh trong khi lại thành công ngay từ lần thử đầu tiên ở những khía cạnh khác. Rất nhiều lần lặp lại như vậy cuối cùng đã tạo ra một tech stack nhàm chán một cách có chủ đích như ngày hôm nay. Backend được viết bằng Python: FastAPI, Postgres cho dữ liệu bản ghi, S3 cho mesh và ảnh. Frontend được render phía server: template Jinja2, HTMX cho các cập nhật trang cục bộ, Tailwind cho styling, và một thành phần client-side thực sự duy nhất: trình xem 3D WebGL dùng để render các mesh. Không có SPA và không có module frontend riêng biệt. Sự kết nối giữa hai stack này là kiểu lâu đời nhất trên web: backend render HTML, trình duyệt hiển thị nó. Khi một trang cần thay đổi, HTMX yêu cầu một fragment và server cũng render fragment đó.
Đây là kiến trúc của ứng dụng đánh giá cốt lõi trong Muse:
![]()
Kiến trúc phân lớp của ứng dụng đánh giá cốt lõi Muse. Các phụ thuộc chỉ hướng xuống dưới.
Có hai đặc tính của cấu trúc này quan trọng đối với agent. Thứ nhất, trạng thái frontend nằm ở phía server. HTML mà người dùng nhìn thấy chính là HTML mà backend đã tạo ra, vì vậy một bài test (hoặc một agent) có thể assert trực tiếp trên đó mà không cần vận hành một framework client-side để dựng lại những gì người dùng đã thấy. Thứ hai, mọi phụ thuộc đều được máy móc thực thi và chỉ hướng xuống dưới. Một thư mục feature không được import thư mục feature anh em (sibling) của nó; nếu hai feature cần đến nhau, composition root sẽ tiêm (inject) một năng lực từ feature này sang feature kia, để phụ thuộc đó chỉ hiển thị ở đúng một file duy nhất. Mỗi lớp chỉ đảm nhận một nhiệm vụ, và các nhiệm vụ đó được ghi rõ ở nơi agent chắc chắn sẽ vấp phải:
| Lớp | Vai trò | Vì sao agent có thể làm việc an toàn ở đây |
|---|---|---|
main/ | Chỉ dùng để nối dây (wiring). Đăng ký các route URL của từng feature module. | Một feature mới chỉ là một thư mục cộng với một dòng include. Rất ít chỗ để "ảo giác" (hallucinate). |
apps/<feature>/ | Mỗi feature một thư mục: routes, pages, service, store, tests. | Bán kính ảnh hưởng của một thay đổi chỉ giới hạn trong thư mục đó. Import chéo giữa các feature anh em là lỗi build, không phải một comment review. |
adapters/ | Thế giới bên ngoài: render web, database, object storage. | Mọi I/O đều đi qua đúng một điểm nối (seam) cho mỗi hệ thống. Mỗi seam đều có bản fake tương ứng. |
libs/ | Các thư viện dùng chung, có thể cài đặt độc lập. | Được đánh version như mã bên thứ ba. Thay đổi một thư viện là việc có chủ đích và rất "ồn ào". |
Các phụ thuộc chỉ bao giờ đi xuống theo danh sách này, và chiều đi đó được một build check thực thi, chứ không phải chỉ là một quy ước.
Ba đặc tính bổ sung khiến codebase này có chủ đích trở nên dễ đọc hiểu đối với agent:
- Mỗi thư mục đều mang theo tài liệu riêng của nó (service đó làm gì, các quy tắc phân lớp, các lệnh chính xác chặn việc merge), để một agent lần đầu tiếp cận có thể tự định hướng mà không cần hỏi hay tự bịa ra thông tin.
- Các module không đơn giản đều mang docstring có backlink tới tài liệu thiết kế, để phần tại sao luôn tồn tại bên cạnh phần là gì.
- Không file nào được vượt quá 500 dòng, có cơ chế ratchet cho một vài trường hợp ngoại lệ. Với các ngoại lệ đó, file chỉ được phép nhỏ đi, không bao giờ được phép lớn trở lại. Context window là hữu hạn; kiến trúc này tôn trọng điều đó.
Tiến độ không nên chỉ nằm trong một căn phòng nhỏ
Ngày nay, khi các agent LLM đã mạnh hơn nhiều so với năm 2025, chúng tôi muốn việc phát triển Muse trở nên hoàn toàn tự động. Nghĩa là, con người chỉ cần chỉ định agent vào đúng task. Agent sẽ tự định hướng xung quanh codebase và bối cảnh của task, tự đi qua các bước triển khai, CI, và code review, rồi cuối cùng tự vận hành pull request đến khi được merge. Con người chỉ quyết định độ ưu tiên của các issue, và đưa ra các quyết định thiết kế then chốt.
Ý tưởng đầu tiên khi nghĩ về phát triển hoàn toàn tự động thường là các nền tảng được xây dựng riêng: bảng điều khiển điều phối, không gian làm việc dành riêng cho agent, hết ứng dụng này đến ứng dụng khác hứa hẹn quản lý toàn bộ đội quân agent của bạn. Chúng tôi đã xem xét, và từ chối. Không phải vì chúng không phải sản phẩm tốt, mà một lần nữa, vì vấn đề khả năng đọc hiểu (legibility).
Chúng tôi muốn công việc của agent phải dễ đọc hiểu đối với con người, cũng giống như codebase phải dễ đọc hiểu đối với agent. Chúng tôi là một công ty có quy mô không nhỏ. Đặt việc phát triển tự động vào trong một bảng điều khiển tùy biến riêng sẽ khiến tiến độ của nó chỉ hiển thị được với số ít người thường xuyên "sống" trong bảng điều khiển đó.
Vì vậy, một lần nữa, bộ công cụ cộng tác và phát triển được chọn một cách có chủ đích để nhàm chán. Mọi thứ trong câu chuyện của chúng tôi đều chạy trên bốn công cụ mà mọi công ty phần mềm đã mở sẵn: Slack, Linear, GitHub, và Claude Code. Điều này khiến tiến độ mà agent tạo ra có thể quan sát được và tìm kiếm được y hệt như tiến độ của bất kỳ thành viên nào trong đội. Một quản lý sản phẩm mở các issue trên Linear và kiểm tra trạng thái tính năng theo đúng cách họ vẫn luôn làm. Bug bot của GitHub hay bước security review đọc các thread PR theo đúng cách nó vẫn luôn làm. Bất kỳ ai báo lỗi cũng dán ảnh chụp màn hình vào đúng kênh Slack như mọi khi, và bản sửa lỗi xuất hiện dưới dạng một pull request bình thường. Không ai phải học thêm bất cứ điều gì để giám sát "cỗ máy" này.
Đây là hình dạng của một issue tự động hoàn chỉnh, từ đầu đến cuối:
![]()
Một issue tự động, từ đầu đến cuối. Con người chỉ chạm vào vòng lặp này ở đúng hai điểm.
Con người chạm vào vòng lặp này ở đúng hai điểm: chỉ định agent vào issue trên Linear, và một vài quyết định mà agent đưa ra trước khi viết mã. Mọi thứ ở giữa hai điểm đó — triển khai, các bước kiểm tra, cuộc trao đổi review, việc merge, và việc triển khai (deploy) — đều chạy mà không có ai ngồi ở ghế lái. Khi một bước kiểm tra thất bại hoặc một reviewer phản đối, agent đọc lỗi, sửa nó, rồi push lại, và vòng lặp đơn giản là chạy thêm một vòng nữa. Nó chỉ dừng lại khi nhánh chuyển xanh (green), mọi phát hiện đã được giải quyết, và hàng đợi (queue) đã merge nó.
Điều khiến việc này khả thi không phải là một tích hợp được xây dựng riêng. Mà là vì ba công cụ này vốn đã lộ ra các bề mặt lệnh (command surface) mà agent có thể điều khiển trực tiếp, giống hệt những gì con người vẫn dùng:
- GitHub, thông qua CLI của nó. Agent mở pull request, đọc lại trạng thái và log của từng CI job, trả lời trong các thread review và đánh dấu chúng là đã giải quyết, và đưa nhánh vào hàng đợi để merge, tất cả đều thông qua các lệnh
ghthông thường. - Linear, thông qua MCP server của nó. Agent đọc issue mà nó được chỉ định vào, theo các liên kết tới những issue liên quan để lấy thêm bối cảnh, chuyển trạng thái, và tạo các issue follow-up cho những việc nó phát hiện dọc đường, tất cả đều dưới dạng tool call hạng nhất.
- Vòng lặp Monitor riêng của Claude Code. CI mất vài phút và hàng đợi merge mất lâu hơn nữa, và không có con người nào đang theo dõi. Thay vào đó, agent theo dõi, và cách nó theo dõi chính là điều biến một lượt chạy không người giám sát thành một thứ thực tế thay vì một "đống lửa" đốt token.
Khi agent mở một pull request, nó không ngồi refresh trang liên tục; nó đăng ký những gì nó đang chờ — lượt chạy CI, các thread review, mục trong hàng đợi merge — rồi hoàn tất lượt (turn) của mình. Tính năng Monitor của Claude Code chỉ gọi lại nó khi trạng thái đang được theo dõi đó thay đổi: một job chuyển đỏ, hội đồng review đăng một phát hiện, hàng đợi merge nhánh đó. Vì agent được đánh thức bởi các tin nhắn từ Monitor về từng cập nhật trạng thái, nó sẽ thực hiện hành động tiếp theo tương ứng, chẳng hạn như lấy log của job thất bại rồi push bản sửa, trả lời một thread review và đánh dấu đã giải quyết, hoặc đưa nhánh vào hàng đợi ngay khi mọi cổng đã xanh.
Không có điều nào trong số này là đặc thù riêng cho Muse. Bất kỳ repository nào có thể truy cập được bởi ba công cụ này đều có thể được vận hành theo cùng một cách, và đó chính xác là lý do chúng tôi chọn chúng.
Niềm tin là một sản phẩm đầu ra của build
Chúng tôi không tin tưởng bản thân LLM. Chúng tôi tin tưởng agent LLM khi nó nằm bên trong một harness, và harness đó có thể được thiết kế để trở thành người bảo vệ cho độ tin cậy. Cụ thể, harness ở đây chính là pipeline CI/CD của chúng tôi cộng với lớp review nằm bên trên nó. Đây là những gì mọi pull request đều phải trải qua, không có ngoại lệ:
![]()
Mọi pull request đều đi qua cùng một harness: các bước kiểm tra tự động trước, sau đó đến hội đồng đánh giá AI.
Từng job riêng lẻ, cùng thời gian điển hình đo được trên các lượt chạy PR thực tế gần đây:
| Job | Những gì nó thực thi | Thời gian điển hình |
|---|---|---|
| Guardrails | Phân lớp (không import chéo giữa các thư mục anh em), hình dạng thư mục, giới hạn kích thước file, quy tắc đặt tên | ~60 s |
| Lint | Định dạng và quy tắc lint của ruff | ~30 s |
| Type check | pyrefly, trên toàn bộ dự án | ~20 s |
| Unit + integration tests | pytest chạy trên một Postgres thật được dựng từ schema | ~90 s |
| Visual tests | Ảnh chụp màn hình Playwright được so sánh (diff) với các golden đã commit, trên renderer riêng của CI | ~140 s |
| Security | Phân tích tĩnh semgrep cộng với quét secret | ~40 s |
| Container boot test | Ứng dụng phải khởi động được, và nó từ chối khởi động trừ khi mọi route đều khai báo ai được phép gọi nó | ~215 s |
Các guardrail chính là kiến trúc mà chúng tôi vừa mô tả, được chuyển hóa thành máy móc. Một import chéo giữa các thư mục anh em ở đây không phải là một comment review; nó là một build đỏ. Các golden ảnh được chụp trong chính môi trường render của CI, vì vậy câu "chạy được trên máy của tôi" không bao giờ có thể là lý do để merge một lỗi vào main.
Lớp đánh giá AI mới là nơi mọi thứ trở nên thú vị. Mọi PR đều được đọc bởi hội đồng gồm ba agent reviewer coding của riêng chúng tôi, cùng đọc một diff, đối chiếu với các nguyên tắc thiết kế đã được viết thành văn bản của repo, mỗi agent được gán một persona khác nhau. Dưới đây là bản phác thảo các prompt được đưa cho từng reviewer.
Kỹ sư trưởng (Principal Engineer):
Persona: Kỹ sư trưởng (Principal Engineer), đánh giá thiết kế, phạm vi lớn
Bạn đánh giá ở phạm vi hệ thống: liệu thay đổi này có thuộc đúng chỗ hay không, có phù hợp với kiến trúc hay không, có tái sử dụng những gì đã tồn tại hay không, và có mang tính bổ sung (additive) hay không — chứ không phải liệu một dòng mã có gọn gàng hay không.
Bạn chỉ thấy diff + các Guides, không thấy toàn bộ repo. Đánh giá việc tái sử dụng và tính nhất quán của pattern ...; đừng khẳng định một cuộc tìm kiếm toàn repo mà bạn không thể thực hiện được.
Góc nhìn của bạn: Đối chiếu diff với những phần liên quan trong các Guides đã cung cấp ...
Phép thử (Litmus): "Nếu tôi là principal engineer của codebase này, tôi có trả lại thay đổi này vì nó nằm sai chỗ, trùng lặp với một năng lực đã có sẵn, hay tạo thêm một cách thứ hai để làm một việc vốn đã có lời giải hay không?" Nếu không, trả về
[].
Kỹ sư cao cấp (Senior Engineer):
Persona: Kỹ sư cao cấp (Senior Engineer), logic / triển khai / chất lượng
Bạn đánh giá trong phạm vi thay đổi: logic có đúng, sạch, có thể test được, và an toàn hay không. Đây là lượt đánh giá cẩn trọng nhất, hãy lần theo từng luồng code, đừng đọc lướt. Tập trung vào diff và các file mà nó chạm tới (đọc toàn bộ các file đó).
Góc nhìn của bạn: Đối chiếu diff với những phần liên quan trong các Guides đã cung cấp ...
Phép thử (Litmus): "Tôi có chấp thuận logic này không, hay tôi sẽ tìm ra một lỗi, một điểm không thể test được, hay một sai sót bảo mật khi đọc kỹ?" Hãy nêu cụ thể input hoặc luồng code nào gây lỗi. Nếu logic ổn, trả về
[].
Kỹ sư QA (QA Engineer):
Persona: Kỹ sư QA (QA Engineer), nó có chạy đúng không, có được test không, có gây lỗi không
Bạn đánh giá hành vi và rủi ro: liệu thay đổi này có làm đúng những gì nó tuyên bố hay không, có được test bao phủ hay không, và liệu nó có thể làm hỏng thứ gì đó đang chạy live hoặc trên production hay không? Đọc tiêu đề và mô tả PR, sau đó kiểm tra xem diff có thực sự thực hiện đúng điều đó hay không.
Góc nhìn của bạn: Khớp ý định (intent-match) ...; Độ bao phủ test ...; Rủi ro regression ...; Các bẫy khi lên production ...
Phép thử (Litmus): "Nếu tôi là QA ký duyệt, tôi có chặn thay đổi này vì nó không làm đúng những gì nó tuyên bố, vì nó đưa hành vi chưa được test lên production, hay vì một thay đổi làm hỏng production hay không?" Nếu nó an toàn và được bao phủ đầy đủ, trả về
[].
Ngoài việc cùng nhìn vào một diff mã từ những góc độ khác nhau, cả 3 reviewer còn chia sẻ một bộ quy tắc chung chi phối việc phát triển của toàn bộ codebase, tức là các Guides:
- Những thứ thay đổi cùng nhau thì sống cùng nhau; những thứ không liên quan thì tách biệt được. Thay đổi hoặc xóa một concern chỉ chạm vào đúng một chỗ.
- Logic nghiệp vụ không phụ thuộc vào chi tiết của thế giới bên ngoài. Một hàm logic nghiệp vụ chạy trong một bài test với các fake được truyền vào, không có I/O thật. Thay một backend khác không đụng đến logic nghiệp vụ. Ứng dụng không bao giờ tự gọi endpoint HTTP của chính nó.
- Tái sử dụng, đừng phát minh lại. Mỗi năng lực chỉ có một cách triển khai duy nhất; một consumer mới gọi interface đã có sẵn thay vì sao chép nó.
- Thêm tính năng bằng cách thêm mới, không phải bằng cách chỉnh sửa. Thêm một tính năng nghĩa là "file mới + một dòng đăng ký", và xóa nó không đụng đến bất kỳ tính năng nào khác.
- Chỉ một cách đã được thiết lập để làm mỗi việc. Người mới sao chép pattern đã có sẵn thay vì phải lựa chọn, và các bài test không cần server hay DB.
- Xóa mã ngay khi nó không còn được sử dụng. Không có gì được ship mà không có gì gọi tới; "giữ lại phòng khi cần" không phải là lý do, vì git vẫn còn nhớ.
- Ưu tiên thứ đơn giản nhất mà vẫn hoạt động; chỉ thêm độ phức tạp khi bị bắt buộc. Mỗi abstraction phải chứng minh được vị trí của nó bằng một nhu cầu cụ thể hiện tại. Thay đổi phải là thay đổi nhỏ nhất đủ để giải quyết vấn đề.
Hội đồng đăng mỗi phát hiện chặn merge dưới dạng một comment inline, gắn đúng vào dòng mã cụ thể, và những comment đó chặn việc merge cho tới khi chúng được giải quyết.
Một đánh giá từ một PR tính năng gần đây trông như thế này:
[medium] Rủi ro regression. Hàm này là điểm nghẽn (chokepoint) dùng chung cho việc tạo và đồng bộ; giờ đây nó gộp các file có hậu tố view vào thành một hàng nhóm duy nhất. Bất kỳ dataset nào đã được nạp từ trước theo cơ chế cũ, ở lần đồng bộ tiếp theo, sẽ bị nhóm lại một cách âm thầm, xóa mất các identity mẫu cũ mà các evaluation đang chạy vẫn còn tham chiếu tới. Không có bài test nào bao phủ việc đồng bộ lại một dataset đã tồn tại theo cách nhóm mới này.
Phát hiện đó là chính xác. Đoạn mã đã vượt qua toàn bộ test suite và một vòng CI xanh đầu tiên. Các bài test là đúng đối với đoạn mã như đã viết. Reviewer nhận ra rằng đoạn mã như đã viết lại sai đối với dữ liệu mà chúng tôi thực sự có. Claude đã sửa nó, thêm bài test regression còn thiếu, trả lời trong thread, và giải quyết phát hiện đó. Toàn bộ cuộc trao đổi đều công khai trong PR, hệt như cách bạn sẽ hướng dẫn (mentor) một kỹ sư junior thông qua một buổi code review.
Đó chỉ là một PR. Khi vận hành agent theo thời gian, chúng tôi còn phát hiện thêm một số pattern điển hình khác:
- Một bản sửa lỗi lại gây ra một vấn đề mới. Một công cụ chẩn đoán mà chúng tôi đã ship đo tốc độ tải xuống. Đánh giá phát hiện phép tính dùng MB (1,000,000 byte) trong khi server gửi dữ liệu theo MiB (1,048,576 byte), khiến mọi tốc độ hiển thị đều thấp hơn thực tế 5%. Vài lượt push sau đó, đánh giá lại chỉ ra rằng bản sửa lỗi để lại hai hằng số phải được đồng bộ thủ công với nhau. Vì vậy nó yêu cầu tính tốc độ trực tiếp từ số byte thực nhận được thay vì làm vậy. Reviewer đã bắt được một lỗi do chính việc xử lý yêu cầu trước đó của nó gây ra.
- Các use case dẫn vào ngõ cụt. Cùng một trang được ship với đường link hiển thị cho mọi người dùng đã đăng nhập, dù bản thân trang đó yêu cầu một quyền cụ thể. Bất kỳ ai không có quyền đó nhấp vào sẽ gặp lỗi 403. Không có bài test nào thất bại, vì đường link vẫn hiển thị và cổng phân quyền vẫn hoạt động đúng. Reviewer QA đối chiếu hai điều này và chặn việc merge cho tới khi đường link chỉ hiển thị cho những người thực sự có thể mở được trang đó.
- "Hack" để vượt qua test. Bài test của một bản sửa lỗi tự tạo ra chính xác exception mà nó lẽ ra phải bắt được, nên nó vẫn pass ngay cả khi việc xử lý lỗi thật sự bị hỏng, vì bài test nhận được đúng thông báo exception "chuẩn". Hội đồng đã phát hiện ra điều này, và bài test được viết lại sẽ cho lỗi thật chạy qua mã thật. Cùng PR đó còn có một bài test pass trên máy của developer nhưng fail trên CI, vì nó âm thầm phụ thuộc vào một file credentials chỉ tồn tại trên máy dev của chúng tôi. Cả hai đều là lỗi nằm trong chính các bài test, và bị bắt bởi lớp nằm bên trên chúng.
Chính sách merge của chúng tôi là 0 phát hiện từ đánh giá AI. Mọi phát hiện đều phải được sửa, hoặc bị bác bỏ (rebut) kèm bằng chứng, trước khi hàng đợi nhận PR đó. Chúng tôi biến việc bác bỏ thành một lựa chọn khả thi vì hội đồng đánh giá gắn cờ dư ở một mức có thể chấp nhận được. Việc viết rõ tại sao một phát hiện là sai, đến lượt nó, đôi khi lại bắt được một sai lầm trong chính lập luận của hội đồng đánh giá.
Sau khi qua các bước kiểm tra, hàng đợi merge sẽ tái xác thực mọi nhánh so với đầu nhánh main đang liên tục di chuyển, và một lượt merge sẽ tự động triển khai lên production mà không có con người nào trong vòng lặp.
![]()
Sau khi qua mọi cổng kiểm tra, hàng đợi merge tái xác thực nhánh đó và một lượt merge sẽ triển khai lên production.
Số dòng mã chỉ là một phần nhỏ của câu chuyện
Qua nhiều PR tự vận hành, chúng tôi phát hiện một pattern vừa đáng chú ý vừa hợp lý: thời gian để lặp lại qua các bước kiểm tra và đánh giá dài hơn nhiều so với thời gian viết bản triển khai đầu tiên, và tỷ lệ này cũng khác biệt rất lớn tùy theo loại task. Ở đây chúng tôi so sánh hai loại task phát triển trong cùng một tuần.
Trường hợp A: gỡ bỏ một tính năng đã chết. Một tính năng legacy nhỏ được đưa offline; đường link, hộp thoại xác nhận, bản dịch, các bài test, và baseline ảnh chụp màn hình của nó đều phải bị loại bỏ. Từ lúc issue được tạo trên Linear đến khi PR được merge vào main chỉ mất 16 phút, trong đó 2 phút đầu dùng để viết mã. Các đánh giá chỉ mang tính khuyến nghị (advisory-only).
Trường hợp B: một cải tiến lớn cho tính năng chẩn đoán mạng. Đây là một thay đổi có kích thước tương đương xét theo số dòng mã, với phiên bản đầu tiên hoàn thành trong 5 phút, trong khi tổng thời gian để merge là khoảng 3 giờ. Trong suốt quá trình đó, agent đã giải quyết 5 vòng đánh giá và 7 phát hiện chặn merge.
Các mốc thời gian cũng khác biệt đáng kể:
| Trường hợp A: gỡ bỏ tính năng đã chết | Trường hợp B: cải tiến công cụ chẩn đoán | |
|---|---|---|
| Kích thước thay đổi | +10 / -125, 8 file | +439 / -19, 9 file |
| Phiên bản chạy được đầu tiên | 2 phút | 5 phút |
| Từ lúc tạo issue đến khi merge | 16 phút | 3 giờ |
| Lâu hơn bao nhiêu so với phiên bản đầu tiên để merge | 8x | 36x |
| Số phát hiện đánh giá chặn merge | không có (chỉ khuyến nghị) | 7, qua 5 vòng |
| Trường hợp xấu nhất nếu mã sai | một đường link không ai nên bấm vào | mọi người dùng đều tin vào một con số sai |
![]()
Hai task có số dòng mã tương đương, nhưng nỗ lực xác thực tỷ lệ theo rủi ro.
Trường hợp thứ hai mất thời gian gấp mười một lần không phải vì mã khó viết hơn, mà vì rủi ro khác nhau, và nỗ lực xác thực được bỏ ra tương xứng với điều đó. Trường hợp A là một việc xóa bỏ: kết quả xấu nhất là một đường link hỏng mà lẽ ra không ai nên truy cập. Trường hợp B là một công cụ đo lường: kết quả xấu nhất là mọi người dùng đều tin vào một con số sai. Hội đồng đánh giá phát hiện ra các lỗi, rồi dành thêm ba vòng nữa để từ chối các bản sửa nửa vời cho tới khi việc xử lý lỗi được gõ kiểu đầy đủ, hoàn chỉnh, và được test kỹ lưỡng. Mỗi vòng khiến bản triển khai trở nên chặt chẽ hơn một cách rõ rệt. Không lỗi nào trong số đó khiến một bài test thất bại, và tất cả chúng đều đã có thể được ship.
Đây chính là phần mà việc sinh mã one-shot vốn dĩ sẽ sai ngay từ cách nó được xây dựng. Sinh mã one-shot tối ưu hóa cho chỉ số dễ nhìn thấy nhất: thời gian từ prompt đến khi có mã biên dịch và chạy được. Cả hai trường hợp của chúng tôi đều đạt chỉ số đó trong vài phút, và nếu chúng tôi dừng lại ở đó, Trường hợp B đã "xong" chỉ vài phút sau khi bắt đầu — và sai trong suốt nhiều tháng.
Cùng một pipeline đã cho một lượt xóa bỏ đi qua trong 16 phút lại giữ một công cụ đo lường lại tới 3 giờ, mà không có con người nào quyết định mỗi cái đáng được đối xử ra sao. Khi mọi người hỏi liệu mã do AI viết có thể được tin tưởng trên production hay không, đây chính là câu trả lời: mã đáng tin cậy đúng bằng với mức độ xác thực được bọc quanh nó, và độ sâu của việc xác thực giờ đây là thứ bạn mua bằng giờ agent thay vì ngày công kỹ sư. Một báo cáo tốc độ sai sẽ tốn kém hơn ba giờ đồng hồ ngay lần đầu tiên ai đó dựa vào nó để xử lý một khiếu nại về mạng.
Những gì chúng tôi học được và bước tiếp theo
Ngày nay, tính tự động thiên về thuộc tính của harness nhiều hơn là thuộc tính của mô hình. Mỗi lần nâng cấp mô hình đều khiến agent của chúng tôi tốt hơn; tuy nhiên, bản thân năng lực mô hình không đi kèm bảo đảm rằng bạn có thể tin tưởng để agent tự do merge pull request của nó. Thay vì đầu tư thêm sự chú ý của con người hay mù quáng dùng mô hình frontier đắt đỏ nhất, chúng tôi chọn xây dựng "đôi tay" và "đôi mắt" để agent được trang bị mọi thứ cần thiết nhằm tạo ra mã production đúng đắn. Và khi năng lực mô hình tiếp tục phát triển, điều này cũng giúp chúng tôi "lướt theo con sóng" thay vì phải xây lại giàn giáo (scaffold) để bù đắp cho giới hạn của các mô hình.
Vai trò của con người trở nên cô đọng hơn, chứ không biến mất. Điều còn lại là phần cốt lõi không thể thu gọn thêm được nữa: quyết định điều gì đáng để xây dựng, viết nó ra đủ rõ ràng, và vận dụng khả năng phán đoán ở những thời điểm mà máy móc gắn cờ là mơ hồ. Gu thiết kế (design taste) và chất lượng của bản đặc tả giờ đây là kỹ năng kỹ thuật có đòn bẩy cao nhất trong đội ngũ này.
Và điều thú vị nhất rút ra được về các đội agent là giá trị của đội ngũ đến từ xung đột chứ không phải từ sự hợp tác. Việc chia một task full-stack thành agent frontend và agent backend có thể hợp lý. Nhưng việc để một agent frontend và một agent backend nói chuyện qua lại với nhau chỉ để hoàn thành task thì lại kém hợp lý hơn. Lợi ích của đội ngũ chỉ xuất hiện khi các thành viên có mục tiêu xung đột nhau. Agent phát triển Muse của chúng tôi muốn ship mã. Các agent đánh giá AI của chúng tôi muốn tìm ra nơi mã sai hoặc được thiết kế kém. Chính sự căng thẳng giữa chúng khiến kết quả trở nên đáng tin cậy, và phản ánh trung thực khối lượng công việc thực sự của một task phát triển.
Quan trọng hơn, chúng tôi nhận thấy rằng những khám phá của mình đang dẫn tới một cách phát triển phần mềm mới đang hình thành: một tổ chức kỹ thuật tự động. Mặc dù chúng tôi đã chứng minh được rằng agent có thể tự động hoàn thành từng task riêng lẻ với các công cụ hiện có, vẫn còn một vài mảnh ghép còn thiếu và những câu hỏi mở mời gọi những nỗ lực trong tương lai:
- Chúng ta có thể mở rộng quy mô những hệ thống như vậy đến mức nào? Khi các tổ chức tự động làm việc trên những task và dự án ngày càng lớn hơn, với nhiều agent chạy song song hơn, những vấn đề mới nào sẽ xuất hiện?
- Làm thế nào để chính đầu ra của harness, chẳng hạn như tỷ lệ pass CI ngay lần đầu và các phát hiện từ đánh giá AI, có thể trở thành tín hiệu đánh giá? Làm sao chúng ta đo được hiệu suất của một agent trong tổ chức, và làm sao để nó tự tiến hóa?
- Làm thế nào để gu thiết kế và gu sản phẩm của con người được "gắn" vào những hệ thống như vậy? Nếu kỹ thuật có thể được tự động hóa với chỉ một lượng thưa thớt các phán đoán của con người, chúng ta có thể đi xa đến đâu để giải quyết những câu hỏi về việc nên xây dựng phần mềm cho ai, và nó nên trông như thế nào?
Cuối cùng, cảm ơn bạn đã đọc đến tận những dòng cuối cùng này. Danh sách việc cần làm để tìm ra câu trả lời cho những câu hỏi này, một cách hợp lý, đang nằm trong Linear — nơi agent tiếp theo của chúng tôi, hoặc chính bạn, có thể nhặt nó lên.







