I once asked Antigravity to "implement the validation for my login screen." What came back was a single validation function — no error message rendering, no focus management, no integration with the server-side check. In my head, "validation is complete" meant the entire screen behaving correctly. To the agent, "complete" meant one function being written.
The misalignment had nothing to do with how cleverly the prompt was phrased. We had simply never agreed on what "Done" meant. Today I want to share the practice I've built up since that day for negotiating a Definition of Done with Antigravity agents.
When "Done" isn't shared, the loop never closes
Since 2014 I've been shipping personal apps, growing them to a cumulative 50 million downloads. Along the way I learned that whenever I outsourced work without supplying a definition of done, the work almost always came back needing rework. The same pattern shows up with AI agents.
The painful part is that context decays with every round trip. Conditions you forget to include in the first message can be added in the second or third, but by then the agent's reasoning has moved on, and the original design decisions don't get revisited. Even with well-sized tasks — the kind of "right-sized requests" I covered in Designing Task Granularity in Antigravity — a vague Done definition still leads to mismatches later.
The fix is almost embarrassingly simple: write the Definition of Done into the very first request.
Build Definition of Done in three layers
The framework I use breaks Done into three layers.
Layer 1: Functional requirements — what behavior counts as working
"When the user fills in email and password and submits, the form sends a POST to the server. On a successful response the user is taken to the home screen. On failure an error appears." Most engineers stop here. That's the problem.
Layer 2: Observable behavior — how I will recognize that it works
"Working" and "observable as working" are not the same thing. Code that runs in the agent's sandbox and code that I can confirm is running in front of me are different artifacts. The gap between the two is exactly where the most painful end-of-day surprises happen — features that "passed" yesterday turn out to have never been verified by a human.
- The POST request is visible in the browser's network panel
- On error, an error message appears below the input field in red, 14px text
- The submit button is disabled while the request is in flight, preventing double submission
If you skip this layer, the agent will write code that runs, but not code that you can confirm runs. You can't trust the completion report unless Layer 2 is written down.
Layer 3: Operational constraints — what must not break
The "while a new feature works, something else quietly broke" failure mode has, honestly, become more frequent for me since adopting AI agents. That's why I now spell out what not to touch.
- Don't modify the existing user-settings validation logic — reuse it as a shared module
- Don't break landscape layouts on smartphones
- Add tests alongside the new feature
This third layer constrains the agent's freedom, and that constraint is what shortens review time the most. There is a counterintuitive lesson here. Adding constraints does not make the agent slower; it makes the agent more decisive, because the search space of acceptable solutions has been narrowed before the work begins. I started writing Layer 3 reluctantly, expecting it to feel like over-engineering for solo development. Instead it removed entire categories of "wait, the agent touched the wrong file" incidents.
A prompt template you can paste
Here is the template I currently keep in my snippets and paste at the top of any non-trivial Antigravity prompt.
Task: [What to build]
Definition of Done:
1. Functional requirements
- [What the user does, and what should happen]
2. Observable behavior
- [Concrete steps I can run to verify]
- [Error display spec — location, color, exact text]
3. Operational constraints
- [Files / modules that must not be touched]
- [Existing behavior that must not change]
- [Tests that must accompany the change]
Before reporting completion, score yourself against each item above
and explicitly mark any item as "not met" if it isn't fully satisfied.The final sentence — "score yourself and mark any unmet item" — is the workhorse. It nudges the agent into auditing its own output against the Definition of Done. The "looks complete to me" responses go away once the agent is forced to compare the output to the criteria item by item.
A practical note on length: the template above looks long, but in practice each layer often has just two or three bullet points. The goal is not exhaustive specification — it is making sure that the parts of "Done" you would have evaluated mentally during review are visible to the agent before the work starts. Treat it as the contract you would have written down anyway, just earlier in the process.
Hand the agent an acceptance script it can run
You can take this further by handing the agent the Definition of Done as executable tests. This pairs naturally with Designing agent completion verification in Antigravity.
// acceptance/login-validation.spec.ts
describe('Login Validation — Definition of Done', () => {
it('Done-1: shows an error for an invalid email', async () => {
await page.fill('#email', 'invalid');
await page.click('button[type=submit]');
await expect(page.locator('.email-error')).toHaveText('Please enter a valid email');
});
it('Done-2: returns focus to the empty password field', async () => {
await page.fill('#email', 'user@example.com');
await page.click('button[type=submit]');
await expect(page.locator('#password')).toBeFocused();
});
it('Done-3: surfaces a network error on connection failure', async () => {
await page.route('**/api/login', route => route.abort());
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'password');
await page.click('button[type=submit]');
await expect(page.locator('.network-error')).toHaveText("Can't reach the network");
});
});When you hand this to Antigravity and say "Done means all of these tests pass," the agent's behavior tightens noticeably. The output you get when there is a concrete target — these tests must pass — is meaningfully different from what you get when the goal is the abstract phrase "build a login screen."
Three failure modes I see when Done is missing
Before adopting this practice I kept hitting the same three failure modes, and noticing them is part of what made the change feel necessary.
The first is interface-first / behavior-last. The agent renders a beautiful screen, the buttons line up, the spacing is correct — and none of them do anything when clicked. This happens when only Layer 1 of Done is in the prompt and Layer 2 is missing. The agent has no signal that "wired up" matters more than "looks correct."
The second is scope creep without notice. I ask for a small change, and quietly a shared utility module gets renamed across the codebase, or the agent decides to "improve" the lint config. Layer 3 was missing, so the agent assumed any improvement was welcome. With Layer 3 in place the agent will pause and explicitly ask before touching anything outside the named scope.
The third is quiet completeness. The agent reports completion, the code looks fine, and a week later I notice an edge case that was never even attempted. This one is the most expensive, because it doesn't surface in the same review session. The "self-score against Definition of Done" instruction at the end of the prompt is what closes this gap. The agent now flags missing edge cases out loud rather than silently skipping them.
Watching for these three patterns in your own work makes the value of writing Done explicit a little easier to feel.
What art juries taught me about written criteria
A side note: alongside development, I also work as an artist, and I've received seventeen international art awards over the years. Submitting work to juries in different countries has taught me something I keep coming back to.
Competitions that publish their judging criteria — say, "30% conceptual originality, 30% technical execution, 40% social impact" — produce results that feel right. Both the artists and the judges share the same vocabulary for talking about the work. Competitions that hide their criteria leave artists guessing what to polish, and outcomes drift toward whatever happens to suit the jury that day.
The relationship with an AI agent looks structurally identical to me. When Done is named, the agent knows what to polish. When it isn't, the agent falls back to a kind of statistical average of what "complete" usually means, and slowly drifts away from what we actually had in mind.
Engineering and art look like different worlds, but the act of agreeing on what counts as finished turns out to be the same conversation in both.
What to try next
Pick one task you're about to send to Antigravity where Done feels fuzzy, and rewrite it using the three-layer template above. A single task is enough to feel the difference in how few rework rounds you need. I admit my first reaction to writing this much upfront was "this feels heavy" — but the time I would have spent on round-trip fixes was always longer than I expected.
Thanks for reading along.