What is an AI coding agent and how it changes developer work
1. Overview
A few years ago, “AI-assisted programming” was mostly about suggesting the next few lines of code. Today, many tools have taken a step further. They read the codebase, run commands, fix multiple files, and verify the results themselves. This type of tool is called an AI coding agent.
This article explains what AI coding agents are and why this topic matters. Then it goes into how to apply them safely in a Java/Spring Boot project. After reading, you’ll know how to write guide files for agents, limit command execution permissions, use tests as an arbiter, and understand when you should not delegate work to an agent.
2. What is an AI coding agent?
You can arrange AI tools for developers by their level of autonomy:
- Autocomplete: suggests a line or block of code right in your editor. You decide every keystroke.
- Chat assistant: you ask, it answers with code. You copy, run, and fix things yourself.
- Coding agent: you give it a goal, like “fix the failing test in
OrderServiceTest”. The agent plans on its own, calls tools (read files, search code, run builds, run tests), sees the results, and repeats until it succeeds or gets stuck.
The core difference lies in the action – observe loop. An agent doesn’t just generate text. It acts on the environment and uses real feedback (compiler, tests, logs) to adjust the next step.
2.1. The loop of an agent
To see the mechanism clearly, here’s a minimal simulation. The “brain” part (language model) is replaced with a fixed ScriptedPlanner so the code runs without any API:
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(); // enough info, stop
};
}
}
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 stops at step " + step);
break;
}
Action a = action.get();
String result = tools.get(a.tool()).apply(a.input());
observations.add(result);
System.out.printf("Step %d: %s(%s) -> %s%n", step, a.tool(), a.input(), result);
}
}
}
Quick explanation:
toolsis the set of tools the agent is allowed to use. In real agents, that’s reading/writing files, running shell commands, searching code, etc.Planner.next(...)receives all previous observations and decides the next action. In a real agent, this is where you call the language model.- The
forloop has a step limit (5). This is an important safety check to prevent infinite loops.
Output:
Step 1: readFile(src/main/java/Order.java) -> class Order { long total() { return 0; } }
Step 2: runTests(mvn -q test) -> FAILED: OrderTest.totalShouldSumItems
Agent stops at step 3
Every coding agent on the market revolves around this framework: decide → call tool → observe → decide again. The difference lies in model quality, the tool set, and context management.
3. Why AI coding agents are getting attention
There are several key reasons, regardless of any specific product:
- Models are good enough to handle multiple steps. When a model can read compiler errors and fix them itself, “delegating a whole task” becomes feasible.
- Iteration still takes a lot of time. Upgrading deprecated APIs, writing tests for old code, renaming things in bulk, fixing static analysis warnings. These tasks are clear, verifiable, and tedious. They suit agents well.
- The tool ecosystem is ready. Build tools, test runners, linters, CI all run via command line. Agents leverage exactly what developers already use.
- Running in parallel. You can delegate several small tasks to agents while focusing on the difficult design work yourself.
However, agents don’t automatically know your project’s conventions, what’s sensitive, or what commands are dangerous. The rest of this article focuses on that.
4. Using AI coding agents in real projects
4.1. Writing guide files for agents
Many agents support reading a guide file at the repo root, often named AGENTS.md or similar depending on the tool. Think of it as onboarding documentation for a very fast team member with no memory:
# Agent Guidelines
## Build & test
- Build: `mvn -q compile`
- Test: `mvn -q test` (must pass before reporting completion)
## Conventions
- Java 21, Spring Boot 3.x. Use constructor injection, not field injection.
- Currency uses `long` (in VND), not `double`.
- Tests use JUnit 5 + AssertJ, named `should...`.
## Forbidden actions
- Never modify `src/main/resources/db/migration/` after release.
- Never add new dependencies without asking.
- Never delete or `@Disabled` tests to "make them pass".
The three most important info groups are verification methods (build/test commands), code conventions, and forbidden zones. The last one prevents a classic mistake: agents “fixing” failing tests by deleting them.
4.2. Limiting commands agents can run
Agents with shell access can damage systems or push code upstream. Many tools support command allowlists. If you’re integrating agents into internal pipelines, add a guard layer before executing any command.
First, add the test dependency (Spring Boot starter already includes JUnit 5 and AssertJ):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
A simple CommandGuard class:
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 collapses extra whitespace so "mvn -q test" can’t bypass the check. A command is allowed only if it starts with an allowed prefix and contains no dangerous tokens. Command chaining operators like &&, ;, | are blocked so agents can’t do mvn -q test && git push.
Tests for this behavior:
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();
}
}
Note: String-based checking is just one defense layer and easy to bypass. For example, the prefix
git diffstill allowsgit diff --output=.... Always run agents in isolated environments like containers or ephemeral VMs, don’t attach production credentials, and limit Git token write permissions.
4.3. Using tests as an arbiter
The most effective way to delegate is write tests first, have agents write code to pass them. Tests are machine-readable specs that can’t be talked around. Suppose you need to calculate order discounts:
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("Jacket", 600_000, 2)));
assertThat(orderService.totalAfterDiscount(order)).isEqualTo(1_080_000);
}
@Test
void shouldNotDiscountSmallOrders() {
Order order = new Order(List.of(new OrderItem("Leaf hat", 150_000, 1)));
assertThat(orderService.totalAfterDiscount(order)).isEqualTo(150_000);
}
}
Then delegate the task with a short, clear completion criteria:
Create records Order, OrderItem(name, unitPrice, quantity) and OrderService
so that OrderServiceTest passes. Don't modify the test file.
Complete when `mvn -q test` has no errors. Summarize changed files.
One possible solution an agent might give:
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;
}
}
Your job now is review. Is the threshold >= or >? Current tests don’t distinguish. That’s a signal to add boundary tests (exactly 1,000,000 VND) before merging. Agents are good at making tests pass. Whether tests correctly express business logic is still your responsibility.
4.4. Keep the final checkpoint
Code written by agents must go through the same process as code written by people. A common verification script helps both agents and CI use the same standard:
#!/usr/bin/env bash
set -euo pipefail
mvn -q compile
mvn -q test
git diff --stat # show modified files so reviewers see the scope
set -euo pipefail makes the script exit immediately on any error. Write this command in AGENTS.md as your “definition of done”, then have CI run the exact same script on pull requests.
5. How developer work changes
When using agents regularly, your work focus shifts noticeably:
- From typing code to writing specs. Vague task descriptions produce vague results. Skills in breaking down work, setting constraints, and stating completion criteria become more important than typing speed.
- More reviews, more thoroughly. Code generation accelerates faster than human reading speed. Small pull requests, clean diffs, and good tests become necessary for meaningful reviews.
- Tests and tooling become core assets. Projects with fast tests, clear build commands, and strict linters will squeeze more value from agents. Projects lacking these will have agents mostly guessing.
- Foundational knowledge still decides. You’ll only catch race conditions, SQL injection, or N+1 queries in agent-written code if you understand them.
6. When NOT to use agents and common mistakes
Agents aren’t suitable in these situations:
- Unclear requirements or architecture decisions needed. Agents will pick a direction and implement it very confidently. Design first, delegate execution later.
- Code involving security, payments, or personal data. Agents can help, but every change needs deep review. Don’t let agents auto-merge.
- No automated verification method available. Without tests or a buildable codebase on your machine, agents have no real feedback. Results become pure guessing.
- Actions that can’t be undone. Running migrations on live databases, deleting cloud resources, sending emails to customers.
- When you’re learning new technology. Delegating everything to agents will make you miss chances to understand deeply.
Common mistakes to watch for:
- Agents delete or weaken tests to make them pass. Prohibit this clearly in your guide file and watch the
src/testdirectory in diffs. - Agents invent APIs that don’t exist in your library version. Compiler and test errors will catch most of this, provided you make agents run them.
- Changes spiral beyond scope: reformatting whole files, renaming unrelated variables. Demand minimal diffs and file summaries.
- Credential leakage: agents read
.envor config files with secrets. Exclude these from the agent’s working environment.
Note: Responsibility for code in the repo doesn’t transfer to agents. The person who merges the pull request is still accountable.
7. Conclusion
AI coding agents are the next step after autocomplete and chatbots. They operate in an action-observe loop, calling tools and acting on real feedback, letting them complete multi-step tasks independently.
To use them safely and effectively, you need three things:
- a guide file that clearly states how to build/test, conventions, and forbidden actions;
- limited command permissions in an isolated environment;
- tests as arbiter, plus serious review before merging.
Agents excel at clear, verifiable work. Design, business decisions, and final accountability remain yours.