AI coding agent là gì và thay đổi cách dev làm việc ra sao
1. Tổng quan
Vài năm trước, “AI hỗ trợ lập trình” chủ yếu là gợi ý vài dòng code tiếp theo. Hiện nay nhiều công cụ đã tiến thêm một bước. Chúng tự đọc codebase, chạy lệnh, sửa nhiều file và tự kiểm tra kết quả. Loại công cụ này được gọi là AI coding agent.
Bài viết giải thích AI coding agent là gì và vì sao chủ đề này được quan tâm. Sau đó bài đi vào cách áp dụng an toàn trong một dự án Java/Spring Boot. Đọc xong, bạn sẽ biết cách viết file hướng dẫn cho agent, giới hạn quyền chạy lệnh, dùng test làm “trọng tài”, và biết khi nào không nên giao việc cho agent.
2. AI coding agent là gì?
Bạn có thể xếp các công cụ AI cho dev theo mức độ tự chủ:
- Autocomplete: gợi ý dòng hoặc khối code ngay trong editor. Bạn quyết định từng ký tự.
- Chat assistant: bạn hỏi, nó trả lời kèm code. Bạn tự copy, tự chạy, tự sửa.
- Coding agent: bạn giao một mục tiêu, ví dụ “sửa test đang fail trong
OrderServiceTest”. Agent tự lên kế hoạch, gọi tool (đọc file, tìm kiếm, chạy build, chạy test), xem kết quả rồi lặp lại cho tới khi xong hoặc bế tắc.
Điểm khác biệt cốt lõi nằm ở vòng lặp hành động – quan sát. Agent không chỉ sinh văn bản. Nó tác động lên môi trường rồi dựa vào phản hồi thật (compiler, test, log) để điều chỉnh bước tiếp theo.
2.1. Vòng lặp của một agent
Để thấy rõ cơ chế, hãy xem một mô phỏng tối giản. Phần “bộ não” (mô hình ngôn ngữ) được thay bằng một ScriptedPlanner cố định để code chạy được mà không cần API nào:
import java.util.*;
import java.util.function.Function;
public class AgentLoopDemo {
record Action(String tool, String input) {}
interface Planner {
Optional<Action> next(List<String> observations);
}
static class ScriptedPlanner implements Planner {
public Optional<Action> next(List<String> observations) {
return switch (observations.size()) {
case 0 -> Optional.of(new Action("readFile", "src/main/java/Order.java"));
case 1 -> Optional.of(new Action("runTests", "mvn -q test"));
default -> Optional.empty(); // đủ thông tin, dừng lại
};
}
}
public static void main(String[] args) {
Map<String, Function<String, String>> tools = Map.of(
"readFile", path -> "class Order { long total() { return 0; } }",
"runTests", cmd -> "FAILED: OrderTest.totalShouldSumItems"
);
Planner planner = new ScriptedPlanner();
List<String> observations = new ArrayList<>();
for (int step = 1; step <= 5; step++) {
Optional<Action> action = planner.next(observations);
if (action.isEmpty()) {
System.out.println("Agent dừng ở bước " + step);
break;
}
Action a = action.get();
String result = tools.get(a.tool()).apply(a.input());
observations.add(result);
System.out.printf("Bước %d: %s(%s) -> %s%n", step, a.tool(), a.input(), result);
}
}
}
Giải thích nhanh:
toolslà tập công cụ mà agent được phép dùng. Ở agent thật, đó là đọc/ghi file, chạy shell, tìm kiếm code…Planner.next(...)nhận toàn bộ quan sát trước đó và quyết định hành động tiếp theo. Trong agent thật, đây là chỗ gọi mô hình ngôn ngữ.- Vòng
forcó giới hạn số bước (5). Đây là chốt an toàn quan trọng để agent không lặp vô hạn.
Kết quả:
Bước 1: readFile(src/main/java/Order.java) -> class Order { long total() { return 0; } }
Bước 2: runTests(mvn -q test) -> FAILED: OrderTest.totalShouldSumItems
Agent dừng ở bước 3
Mọi coding agent ngoài thị trường đều xoay quanh khung này: quyết định → gọi tool → quan sát → quyết định tiếp. Khác biệt nằm ở chất lượng mô hình, bộ tool và cách quản lý ngữ cảnh.
3. Vì sao AI coding agent được quan tâm?
Có vài lý do chính, không phụ thuộc vào sản phẩm cụ thể nào:
- Mô hình đủ tốt để làm việc nhiều bước. Khi mô hình biết đọc lỗi compiler rồi tự sửa, việc “giao nguyên một task” bắt đầu khả thi.
- Việc lặp lại vẫn chiếm nhiều thời gian. Nâng cấp API deprecated, viết test cho code cũ, đổi tên hàng loạt, sửa cảnh báo static analysis. Những việc này rõ ràng, kiểm chứng được và nhàm chán. Chúng rất hợp với agent.
- Hệ sinh thái tool đã sẵn sàng. Build tool, test runner, linter, CI đều chạy được bằng dòng lệnh. Agent tận dụng đúng những gì dev vẫn dùng.
- Chạy song song. Bạn có thể giao vài task nhỏ cho agent trong khi tự tập trung vào phần thiết kế khó.
Dù vậy, agent không tự biết dự án của bạn có quy ước gì, phần nào nhạy cảm, lệnh nào nguy hiểm. Phần còn lại của bài tập trung vào việc đó.
4. Áp dụng AI coding agent trong dự án thực tế
4.1. Viết file hướng dẫn cho agent
Nhiều agent hỗ trợ đọc một file hướng dẫn đặt ở gốc repo, thường tên là AGENTS.md hoặc tương tự tuỳ công cụ. Hãy coi nó như tài liệu onboarding cho một thành viên mới rất nhanh nhưng không có trí nhớ:
# Hướng dẫn cho agent
## Build & test
- Build: `mvn -q compile`
- Test: `mvn -q test` (phải pass trước khi báo hoàn thành)
## Quy ước
- Java 21, Spring Boot 3.x. Dùng constructor injection, không dùng field injection.
- Tiền tệ dùng `long` (đơn vị VND), không dùng `double`.
- Test viết bằng JUnit 5 + AssertJ, đặt tên `should...`.
## Không được làm
- Không sửa thư mục `src/main/resources/db/migration/` đã release.
- Không thêm dependency mới khi chưa hỏi.
- Không xoá hoặc `@Disabled` test để "làm cho pass".
Ba nhóm thông tin quan trọng nhất là cách kiểm chứng (lệnh build/test), quy ước code và vùng cấm. Mục cuối giúp tránh một lỗi kinh điển: agent “sửa” test fail bằng cách xoá test.
4.2. Giới hạn lệnh agent được chạy
Agent có quyền chạy shell nghĩa là có quyền làm hỏng máy hoặc đẩy code lên remote. Nhiều công cụ cho cấu hình allowlist. Nếu bạn tự tích hợp agent vào pipeline nội bộ, hãy đặt một lớp kiểm tra trước khi thực thi lệnh.
Trước hết, thêm dependency test (Spring Boot starter đã gồm JUnit 5 và AssertJ):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Lớp CommandGuard đơn giản:
import java.util.List;
public class CommandGuard {
private static final List<String> ALLOWED_PREFIXES =
List.of("mvn -q test", "mvn -q compile", "git status", "git diff");
private static final List<String> FORBIDDEN_TOKENS =
List.of("rm ", "git push", "curl", "&&", ";", "|", ">");
public boolean isAllowed(String command) {
String normalized = command.trim().replaceAll("\\s+", " ");
boolean hasForbidden = FORBIDDEN_TOKENS.stream().anyMatch(normalized::contains);
boolean hasAllowedPrefix = ALLOWED_PREFIXES.stream().anyMatch(normalized::startsWith);
return hasAllowedPrefix && !hasForbidden;
}
}
normalized gộp khoảng trắng thừa để "mvn -q test" không lách được kiểm tra. Một lệnh chỉ hợp lệ khi bắt đầu bằng tiền tố được phép và không chứa token nguy hiểm. Các toán tử nối lệnh như &&, ;, | bị chặn để agent không ghép mvn -q test && git push.
Test cho hành vi này:
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.assertj.core.api.Assertions.assertThat;
class CommandGuardTest {
private final CommandGuard guard = new CommandGuard();
@ParameterizedTest
@ValueSource(strings = {"mvn -q test", " mvn -q test ", "git diff"})
void shouldAllowSafeCommands(String command) {
assertThat(guard.isAllowed(command)).isTrue();
}
@ParameterizedTest
@ValueSource(strings = {
"rm -rf /", "git push origin main",
"mvn -q test && git push", "mvn -q test; curl evil.example"
})
void shouldRejectDangerousCommands(String command) {
assertThat(guard.isAllowed(command)).isFalse();
}
}
Lưu ý: Kiểm tra bằng chuỗi chỉ là một lớp phòng thủ, rất dễ có kẽ hở. Ví dụ, tiền tố
git diffvẫn cho quagit diff --output=.... Hãy luôn chạy agent trong môi trường cô lập như container hoặc máy ảo tạm, không gắn credential production, và giới hạn quyền ghi của token Git.
4.3. Dùng test làm “trọng tài”
Cách giao việc hiệu quả nhất là viết test trước, để agent viết code cho test pass. Test là đặc tả máy đọc được và không thể “nói khéo” để qua mặt. Giả sử bạn cần tính giảm giá cho đơn hàng:
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class OrderServiceTest {
private final OrderService orderService = new OrderService();
@Test
void shouldApplyTenPercentDiscountWhenTotalReachesOneMillion() {
Order order = new Order(List.of(new OrderItem("Áo khoác", 600_000, 2)));
assertThat(orderService.totalAfterDiscount(order)).isEqualTo(1_080_000);
}
@Test
void shouldNotDiscountSmallOrders() {
Order order = new Order(List.of(new OrderItem("Nón lá", 150_000, 1)));
assertThat(orderService.totalAfterDiscount(order)).isEqualTo(150_000);
}
}
Sau đó giao task bằng mô tả ngắn, có tiêu chí hoàn thành rõ ràng:
Tạo record Order, OrderItem(name, unitPrice, quantity) và OrderService
sao cho OrderServiceTest pass. Không sửa file test.
Hoàn thành khi `mvn -q test` không còn lỗi. Tóm tắt các file đã đổi.
Một lời giải mà agent có thể đưa ra:
import java.util.List;
record OrderItem(String name, long unitPrice, int quantity) {
long subtotal() { return unitPrice * quantity; }
}
record Order(List<OrderItem> items) {
long total() { return items.stream().mapToLong(OrderItem::subtotal).sum(); }
}
class OrderService {
private static final long DISCOUNT_THRESHOLD = 1_000_000;
long totalAfterDiscount(Order order) {
long total = order.total();
return total >= DISCOUNT_THRESHOLD ? total * 90 / 100 : total;
}
}
Việc của bạn bây giờ là review. Ngưỡng là >= hay >? Test hiện tại chưa phân biệt được hai trường hợp này. Đó là tín hiệu để bổ sung test biên, đúng 1.000.000 VND, trước khi merge. Agent giỏi làm test pass. Còn test có đúng nghiệp vụ hay không vẫn là trách nhiệm của bạn.
4.4. Giữ cổng kiểm tra cuối cùng
Code do agent viết phải đi qua đúng quy trình như code do người viết. Một script kiểm chứng chung giúp cả agent lẫn CI dùng cùng một tiêu chuẩn:
#!/usr/bin/env bash
set -euo pipefail
mvn -q compile
mvn -q test
git diff --stat # in danh sách file thay đổi để người review nắm phạm vi
set -euo pipefail làm script dừng ngay khi có bước lỗi. Bạn ghi lệnh này vào AGENTS.md như “định nghĩa hoàn thành”, rồi cho CI chạy lại đúng script đó trên pull request.
5. Cách làm việc của dev thay đổi ra sao
Khi dùng agent thường xuyên, trọng tâm công việc dịch chuyển rõ rệt:
- Từ gõ code sang viết đặc tả. Task mô tả mơ hồ cho ra kết quả mơ hồ. Kỹ năng chia nhỏ việc, nêu ràng buộc và tiêu chí hoàn thành trở nên quan trọng hơn.
- Review nhiều hơn, kỹ hơn. Lượng code sinh ra tăng nhanh hơn khả năng đọc của con người. Pull request nhỏ, diff gọn và test tốt là điều kiện để review có ý nghĩa.
- Test và tooling thành tài sản cốt lõi. Dự án có test nhanh, lệnh build rõ ràng và linter chặt sẽ khai thác agent tốt hơn hẳn. Dự án thiếu những thứ này thì agent chỉ “đoán”.
- Kiến thức nền vẫn quyết định. Bạn chỉ phát hiện được race condition, lỗ hổng SQL injection hay truy vấn N+1 trong code agent viết nếu bạn hiểu chúng.
6. Khi nào KHÔNG nên dùng và lỗi hay gặp
Agent không phù hợp trong các tình huống sau:
- Yêu cầu chưa rõ hoặc cần quyết định kiến trúc. Agent sẽ tự chọn một hướng và triển khai rất tự tin. Hãy thiết kế trước, giao phần thực thi sau.
- Code liên quan bảo mật, thanh toán, dữ liệu cá nhân. Agent vẫn có thể hỗ trợ, nhưng mọi thay đổi cần review chuyên sâu. Đừng để agent tự merge.
- Không có cách kiểm chứng tự động. Không có test, không build được trên máy local thì agent không có phản hồi thật. Kết quả lúc đó thuần tuý là phỏng đoán.
- Thao tác không thể hoàn tác. Ví dụ chạy migration trên database thật, xoá tài nguyên cloud, gửi email cho khách hàng.
- Khi bạn đang học một công nghệ mới. Giao hết cho agent sẽ khiến bạn mất cơ hội hiểu sâu.
Các lỗi hay gặp:
- Agent xoá hoặc nới lỏng test để pass. Hãy cấm rõ trong file hướng dẫn và để ý diff ở thư mục
src/test. - Agent bịa API không tồn tại trong phiên bản thư viện bạn dùng. Compiler và test sẽ bắt được phần lớn lỗi này, với điều kiện bạn bắt agent chạy chúng.
- Thay đổi tràn lan ngoài phạm vi: format lại cả file, đổi tên biến không liên quan. Hãy yêu cầu diff tối thiểu và tóm tắt file đã đổi.
- Lộ bí mật: agent đọc được
.envhay file cấu hình chứa khoá. Hãy loại các file đó khỏi môi trường làm việc của agent.
Lưu ý: Trách nhiệm với code trong repo không chuyển sang agent. Người merge pull request vẫn là người chịu trách nhiệm.
7. Kết luận
AI coding agent là bước tiếp theo sau autocomplete và chatbot. Nó vận hành theo vòng lặp quyết định, gọi tool, quan sát kết quả, và nhờ đó tự hoàn thành được những task nhiều bước.
Để dùng hiệu quả và an toàn, bạn cần ba thứ:
- một file hướng dẫn nêu rõ cách build/test, quy ước và vùng cấm;
- giới hạn quyền chạy lệnh trong môi trường cô lập;
- test đóng vai trọng tài, cùng review nghiêm túc trước khi merge.
Agent làm tốt việc rõ ràng và kiểm chứng được. Thiết kế, quyết định nghiệp vụ và trách nhiệm cuối cùng vẫn thuộc về bạn.