AI agents are becoming more capable at writing code, editing files, running tests, and coordinating larger engineering tasks. But as soon as an agent starts working across multiple files, multiple steps, or multiple sessions, a familiar engineering problem appears:
How do we keep the project state clean, recoverable, and understandable?
This is where a surprisingly useful analogy comes in: database transactions.
Databases have spent decades solving the problem of safely changing state. They use the ACID model:
- Atomicity
- Consistency
- Isolation
- Durability
That same mental model can help us design better agent harnesses, not just better agent memory. This post is about the operational layer around the model: the part that makes agent work safe, repeatable, and recoverable.
A harness is the system around the agent: the scripts, rules, files, checks, workflows, prompts, and environment that control how the agent works. The agent may be the “brain,” but the harness is the operational system that keeps the work safe.
Let’s walk through a practical example.
Example: Agent Adds a New API Endpoint
Imagine we have a backend service with an existing REST API. We ask an AI coding agent to add a new endpoint:
Add a new endpoint
GET /customers/{customerId}/payment-summarythat returns a customer’s payment summary. Update the service layer, controller, tests, and API documentation.
This sounds like one task, but underneath it has many steps:
- Understand the current customer/payment domain model.
- Add a method to the service layer.
- Add a controller endpoint.
- Add request/response DTOs if needed.
- Add unit tests.
- Add integration tests.
- Update OpenAPI documentation.
- Run formatting, linting, and tests.
- Commit the change.
Without a good harness, the agent might modify five files, break two tests, forget documentation, and leave the repo in a strange half-finished state.
This is exactly the kind of problem ACID thinking helps with.
Atomicity: One Logical Operation, One Commit
In database terms, atomicity means:
Either the entire transaction succeeds, or none of it does.
For agent engineering, the equivalent is:
Each logical operation should either fully complete and be committed, or be rolled back.
In our example, “add payment summary endpoint” is one logical operation. It should result in one clean commit.
A good harness might tell the agent:
Your task is one atomic operation:
- Modify code
- Add/update tests
- Update docs
- Run verification
- Commit only if verification passes
The agent should not commit after only adding the controller. It should not commit after only updating the service. Those are intermediate states, not completed logical operations.
A clean final commit might look like:
git commit -m "Add customer payment summary endpoint"
The important part is that the commit represents a complete unit of value.
If the agent gets stuck halfway, the harness should give it a safe rollback path:
git status
git diff
git stash push -m "failed attempt: payment summary endpoint"
Or, in a stricter environment:
git reset --hard HEAD
git clean -fd
The exact rollback strategy depends on how much you want to preserve for debugging. But the principle is the same:
No half-done operation should become part of the main project history.
Atomicity gives us a simple rule:
If the operation cannot be completed, it should not be committed.
Consistency: Define What “Good State” Means
In database terms, consistency means the database moves from one valid state to another valid state.
For an agent harness, consistency means:
After the agent finishes an operation, the repo must satisfy clearly defined verification rules.
This is where many agent workflows fail. They rely too much on vibes:
“Looks good.”
“The code seems fine.”
“The agent said it finished.”
That is not enough.
A harness should define concrete consistency checks. For example:
./gradlew test
./gradlew check
./gradlew spotlessCheck
npm run lint
npm run typecheck
For our payment summary endpoint, the harness may define consistency as:
A consistent state means:
- The project compiles.
- All unit tests pass.
- All integration tests pass.
- Lint passes.
- Formatting checks pass.
- OpenAPI documentation is updated.
- No unrelated files are modified.
Now the agent has a clear finish line.
After making changes, the agent should run the verification suite:
./gradlew clean test
./gradlew check
If tests fail, the operation is not done.
If formatting fails, the operation is not done.
If the endpoint works but documentation is missing, the operation is not done.
This changes the agent’s behavior. Instead of treating code generation as the goal, the harness treats verified project state as the goal.
The key idea is:
The agent does not decide that the repo is correct. The verification predicates decide.
This is a powerful shift.
Isolation: Avoid Agents Stepping on Each Other
In database systems, isolation prevents concurrent transactions from interfering with each other.
In agent systems, isolation matters when multiple agents or agent sessions work on the same project.
Imagine three agents working at the same time:
- Agent A adds the payment summary endpoint.
- Agent B refactors the customer controller.
- Agent C updates OpenAPI documentation.
Without isolation, they may edit the same files:
CustomerController.java
CustomerService.java
openapi.yaml
README.md
This creates race conditions. One agent may overwrite another agent’s work. Another may run tests against a mixed state that never existed as a real logical operation.
A simple isolation strategy is to give each agent its own git branch:
git checkout -b agent/payment-summary-endpoint
Agent B gets a separate branch:
git checkout -b agent/customer-controller-refactor
Agent C gets another:
git checkout -b agent/openapi-doc-update
Now each agent has a private workspace. They can make changes, run tests, and commit without corrupting each other’s state.
For file-based state, isolation also matters.
A bad approach is this:
.agent/progress.md
If every agent writes to the same progress file, they can overwrite each other.
A better approach is:
.agent/runs/payment-summary-endpoint/progress.md
.agent/runs/customer-controller-refactor/progress.md
.agent/runs/openapi-doc-update/progress.md
Each agent writes to its own state file.
You can also separate planning files:
.agent/runs/payment-summary-endpoint/plan.md
.agent/runs/payment-summary-endpoint/notes.md
.agent/runs/payment-summary-endpoint/verification.md
This keeps the state understandable and avoids accidental collisions.
Isolation gives us another practical rule:
Agents should not share writable state unless the harness explicitly coordinates access.
For most teams, the easiest isolation boundary is still the oldest and simplest one:
Use git branches.
Durability: Important Knowledge Must Be Written Down
In database terms, durability means that once a transaction is committed, it survives crashes and restarts.
For agent harnesses, durability means:
Important project knowledge must be stored in git-tracked files, not only in the agent’s memory or chat context.
This is one of the most important lessons in agent engineering.
Agents often learn useful things while working:
- “Payment summary excludes failed payments.”
- “Refunds should be represented as negative amounts.”
- “This endpoint must not expose internal transaction IDs.”
- “Integration tests require a seeded customer with completed payments.”
- “The OpenAPI generator fails if examples contain unescaped timestamps.”
If that knowledge only lives in the agent’s temporary context, it disappears when the session ends.
That is not durable.
A better harness asks the agent to write durable knowledge into project files.
For example:
docs/payment-summary-endpoint.md
Or:
.agent/knowledge/payment-domain.md
Or directly into test names and comments where appropriate:
@Test
void shouldExcludeFailedPaymentsFromSummary() {
// ...
}
Durability does not mean writing everything down. Temporary thoughts can stay temporary. But knowledge that future agents, future engineers, or future debugging sessions will need should be persisted.
A useful distinction is:
Temporary state:
- Current reasoning
- Scratch notes
- Failed attempts
- Intermediate command output
Durable state:
- Architecture decisions
- API behavior
- Domain rules
- Setup instructions
- Known edge cases
- Verification commands
The harness can enforce this by requiring a final summary file for larger operations:
.agent/runs/payment-summary-endpoint/final-report.md
Example:
# Final Report: Payment Summary Endpoint
## Changed Files
- `CustomerPaymentController.java`
- `CustomerPaymentService.java`
- `PaymentSummaryResponse.java`
- `CustomerPaymentControllerTest.java`
- `openapi.yaml`
## Behavior
The endpoint returns completed payment totals for a customer. Failed payments are excluded. Refunds are included as negative amounts.
## Verification
Ran:
```bash
./gradlew clean test
./gradlew check
```
Result: passed.
## Notes for Future Work
Pagination is not required because the endpoint returns aggregate summary data, not raw payment records.
This kind of file is incredibly useful. It gives future humans and future agents a durable understanding of what happened.
Durability gives us the rule:
What matters must survive the session.
If it is only in the agent’s head, it does not count.
Putting It All Together
Here is what the full ACID-style agent workflow might look like.
The user gives the task:
Add GET /customers/{customerId}/payment-summary.
Update implementation, tests, and docs.
The harness starts a new isolated branch:
git checkout -b agent/payment-summary-endpoint
The agent creates a run directory:
.agent/runs/payment-summary-endpoint/
It writes a plan:
.agent/runs/payment-summary-endpoint/plan.md
The agent modifies code, tests, and docs.
Then it runs consistency checks:
./gradlew clean test
./gradlew check
If the checks fail, the agent fixes the issue and reruns them.
If the task cannot be completed, the harness rolls back or stashes the failed attempt:
git stash push -m "failed attempt: payment summary endpoint"
If checks pass, the agent writes durable notes:
.agent/runs/payment-summary-endpoint/final-report.md
Then it creates one atomic commit:
git add .
git commit -m "Add customer payment summary endpoint"
Now we have:
- Atomicity: one logical operation became one clean commit.
- Consistency: tests, lint, and documentation checks passed.
- Isolation: the agent worked on its own branch and state files.
- Durability: important knowledge was written to git-tracked files.
This is not theoretical. This is a practical operating model.
Why This Analogy Works So Well
AI agents are probabilistic. They can be very useful, but they can also get distracted, forget constraints, or leave work unfinished.
ACID principles give us a way to compensate for that.
They move reliability out of the agent’s personality and into the harness design.
Instead of hoping the agent behaves carefully, we define a system where careful behavior is the default path.
The agent does not need to remember everything. The harness gives it structure.
The agent does not need to magically know when it is done. Verification checks define done.
The agent does not need to keep all project knowledge in context. Durable files preserve important information.
The agent does not need to coordinate perfectly with other agents. Branches and separate state files create isolation.
This is the bigger lesson:
Good agent engineering is not just about smarter models. It is about better state management.
Final Thought
When people first hear “manage agent state with ACID principles,” it may sound like overengineering.
But it is actually a very practical framework.
Every serious software system eventually needs answers to these questions:
- What happens if the operation fails halfway?
- How do we know the result is correct?
- What happens when multiple workers change things at the same time?
- What knowledge survives after the process ends?
Databases answered these questions with ACID.
Agent harnesses can borrow the same wisdom.
The result is not just cleaner code generation. It is a more reliable engineering workflow.
For me, the core principle is simple:
Treat agent work like a transaction.
Start from a clean state. Make one logical change. Verify it. Persist what matters. Commit only when the system is correct.
That is how agentic engineering becomes less like magic and more like engineering.