Have you been using Background Agent without being quite sure you're using it effectively? You start a task, wait for it to finish, get results that are close but not quite right, and repeat. At some point you start wondering if it's actually worth the effort.
I felt the same way in my first couple of weeks. The turning point came when I stopped treating Background Agent as "async chat" and understood what it actually is at an architectural level. Once I redesigned my task specifications, set up a proper AGENTS.md, and integrated git worktrees, the difference was significant enough to reshape how I structure my development days.
As an indie developer with no one else to review the output, how you handle a result that lands close but not quite right is your productivity. That framing shapes most of the decisions below.
One more thing before we start: an earlier version of this article contained an API I had not verified. I've gone back to the primary documentation and corrected it in full, and I've marked where.
Why Background Agent Is Architecturally Different From "Just Async"
The most important thing to understand about Background Agent is that it runs in a fully isolated Linux virtual machine — not as an async process in your existing environment.
Inside the Sandbox VM
When you launch a Background Agent, Antigravity takes a snapshot of your repository and spins up an isolated VM. This VM:
- Has no direct access to your local environment
- Can reach the internet but cannot connect to your localhost (localhost:3000, local databases, etc.)
- Writes all file changes within its own filesystem, delivering them back as a pull request or commit when complete
- Has no access to your browser's devtools, running processes, or visual rendering
This isolation is both the strength and the constraint. Tasks that require visual feedback, local service connections, or interactive dialogue are a poor fit. Tasks that involve analyzing code, writing tests, generating documentation, or systematic refactoring are where Background Agent consistently delivers.
Choosing Between Background Agent and Inline Agent
Here's the decision framework I use:
Background Agent is the right choice when:
- The task will take more than 5 minutes to complete
- You can fully articulate the success criteria before starting
- The task doesn't depend on your running local environment
- You can productively use the time the agent is working
Inline agent is the right choice when:
- The task requires back-and-forth dialogue
- You need to see visual output as the work progresses
- The task will complete in under 10 minutes
- Design decisions will need to be made mid-task
The "can you articulate success criteria?" question is the most important filter. Vague instructions produce vague results. Clear, testable completion conditions are the single biggest factor in Background Agent output quality.
Designing Task Specifications That Actually Work
Background Agent instructions work nothing like interactive chat. Without the ability to ask clarifying questions mid-task, the quality of your initial specification directly determines the quality of the output.
The Task Specification Template
Here's the template I use for every Background Agent task. My success rate went from roughly 70% to above 90% after adopting this structure:
## Goal
Increase test coverage for the UserProfile component to 80% or above.
Current coverage: approximately 35% (verified with `npx vitest coverage`)
## Target Files
- src/components/UserProfile.tsx (primary target)
- src/components/UserProfile.test.tsx (create or extend)
- src/types/user.ts (reference only, do not modify)
## Testing Framework
- vitest + @testing-library/react
- Use vi.mock() for mocks
- Use @testing-library/user-event for user interactions
## Completion Criteria (all must be satisfied)
1. Coverage reaches 80%+ with `npx vitest coverage`
2. All existing tests pass without modification
3. Zero TypeScript build errors with `npx tsc --noEmit`
4. Each test case has a descriptive describe and it label
## Constraints
- Do not modify existing props interfaces in UserProfile.tsx
- Do not use real endpoint URLs in API mocks
- Do not create snapshot tests (maintenance cost too high)
## Reference
- Auth.test.tsx (src/components/Auth.test.tsx) is a good style reference
- Run tests with: `npx vitest run --reporter=verbose`The Constraints section is what most people skip, and it's what causes the most unpleasant surprises. Background Agent will try to be helpful in ways you didn't ask for. Explicitly stating what not to change prevents the majority of "it fixed this but broke that" situations.
Getting Task Granularity Right
Tasks that are too large are where Background Agent struggles most:
# ❌ Too large — high failure risk
"Refactor the entire project to improve type safety"
# ✅ Right size — completes within a focused session
"Add zod schema validation to all API client functions in src/api/,
validating both inputs and return values"In my experience, Background Agent performs most reliably on tasks equivalent to 1–3 hours of human effort. Tasks that would take half a day or longer are better broken into subtasks and run sequentially. The total time is often shorter because each subtask succeeds cleanly rather than getting tangled.
Pre-loading Context Before Launch
Before starting a Background Agent task, add relevant files as Knowledge Items. The most impactful ones to include:
- Architecture documentation
- Coding conventions and style guides (CONTRIBUTING.md)
- Relevant type definition files (types/ directory)
- Notes on error-prone areas
This is especially valuable for projects with non-obvious constraints. Telling the agent what the project is before it starts reading code reduces both the token usage and the likelihood of wrong assumptions early on.
Designing AGENTS.md to Auto-Inject Project Context
Having to explain your project's constraints in every single task specification is inefficient. A well-designed AGENTS.md is injected automatically into every agent session, eliminating the need for repetitive context-setting.
A Production-Ready AGENTS.md
# Project: MyApp — Agent Working Guidelines
## About This Project
A personal task management SaaS built with Next.js 16 App Router + TypeScript
+ Cloudflare Workers. Bilingual support (English/Japanese) via next-intl v4.
## Critical Technical Constraints
- No Node.js built-ins (Cloudflare Workers environment)
- No `fs` module — all file I/O goes through the ASSETS binding
- `npm run build` requires `node scripts/generate-content.mjs` first
(already configured in prebuild hook — do not remove)
- Test runner: vitest (not Jest — different configuration)
## Coding Conventions
- Type definitions centralized in src/types/
- Mark components as Server/Client explicitly ('use client' directive)
- API routes in src/app/api/, named route.ts
- Prefer descriptive variable names over abbreviations
## Common Mistakes to Avoid
### ❌ Typical errors
- Calling `headers()` synchronously → requires async/await
- Using `cookies().set()` in client components → server components only
- Importing from 'fs' → use ASSETS binding instead
### ✅ Correct patterns
- Environment variables: process.env.NEXT_PUBLIC_* (client) / process.env.* (server)
- Database access: getCloudflareContext().env.DB
## Test Commands
\`\`\`bash
npx vitest run # Run all tests
npx vitest coverage # Coverage report
npx tsc --noEmit # Type check only
\`\`\`
## Files That Must Not Be Modified
- src/config/pricing.ts (contains Stripe price IDs)
- public/robots.txt (SEO configuration)
- wrangler.toml (Cloudflare deployment config)The difference this makes is hard to overstate. Agents no longer need to discover your constraints by running into them — they know upfront.
Directory-Level AGENTS.md
You can place AGENTS.md files in subdirectories as well as at the project root. My typical structure:
project/
├── AGENTS.md ← Project-wide rules
├── src/
│ ├── components/
│ │ └── AGENTS.md ← UI component design rules
│ └── api/
│ └── AGENTS.md ← API route conventions
└── scripts/
└── AGENTS.md ← Notes on script modifications
Running Multiple Background Agents in Parallel
A single Background Agent is already powerful. Multiple agents running simultaneously can meaningfully compress development timelines — but only if you manage file ownership carefully.
Identifying Safe Parallel Tasks
Parallel execution is safe when the tasks have non-overlapping file scope:
# ✅ Safe to run in parallel
Agent A: Add tests in src/components/
Agent B: Add validation in src/api/
Agent C: Update documentation in docs/
# ❌ Dangerous — likely to conflict
Agent A: Improve types in src/types/user.ts
Agent B: Refactor UserProfile component
(both will likely modify user.ts)
Before launching parallel agents, map out which files each task will touch. Any overlap is a signal to serialize those tasks or further refine their scope.
Git Worktree Integration
Git worktrees let each agent work on a completely independent copy of the repository, eliminating merge conflicts during execution. Each agent gets its own branch and filesystem, merging back to main only when both are done.
#!/bin/bash
# setup-parallel-agents.sh
PROJECT_DIR="$HOME/myapp"
WORKTREES_DIR="$HOME/myapp-worktrees"
mkdir -p "$WORKTREES_DIR"
# Create isolated worktrees for each parallel task
declare -A TASKS=(
["test-coverage"]="Add test coverage to src/components/"
["api-validation"]="Add zod validation to src/api/"
["docs-update"]="Update API documentation in docs/"
)
for task in "${!TASKS[@]}"; do
branch="agent/${task}-$(date +%Y%m%d-%H%M)"
worktree_path="$WORKTREES_DIR/$task"
# Remove any leftover worktree from previous run
git -C "$PROJECT_DIR" worktree remove "$worktree_path" --force 2>/dev/null || true
# Create fresh worktree from main
git -C "$PROJECT_DIR" worktree add \
-b "$branch" \
"$worktree_path" \
main
echo "✅ Worktree ready: $worktree_path"
echo " Branch: $branch"
echo " Task: ${TASKS[$task]}"
done
git -C "$PROJECT_DIR" worktree listAfter all agents complete:
#!/bin/bash
# merge-agent-results.sh
PROJECT_DIR="$HOME/myapp"
WORKTREES_DIR="$HOME/myapp-worktrees"
for task in "test-coverage" "api-validation" "docs-update"; do
worktree_path="$WORKTREES_DIR/$task"
if [ ! -d "$worktree_path" ]; then
echo "⚠️ Worktree not found: $task — skipping"
continue
fi
branch=$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD)
echo "Merging $branch..."
# Run tests before merging
cd "$worktree_path"
if ! npm test -- --passWithNoTests 2>/dev/null; then
echo "❌ Tests failed in $task — skipping merge"
continue
fi
# Merge into main
git -C "$PROJECT_DIR" merge --no-ff "$branch" \
-m "feat: agent/$task auto-generated changes"
# Clean up
git -C "$PROJECT_DIR" worktree remove "$worktree_path"
git -C "$PROJECT_DIR" branch -d "$branch"
echo "✅ Merged and cleaned up: $task"
doneThis merge loop breaks on the third agent
The loop above looks reasonable. It isn't, and I found out on the day I moved to three agents in parallel.
git merge returns exit 1 on a conflict and leaves the repository with MERGE_HEAD in place — but the loop keeps going. The next iteration calls git merge again, fails with "you have not concluded your merge," and by the third task the screen tells you nothing useful. The original conflict message has been buried under two derived errors.
git worktree remove has the same character. If an agent left uncommitted changes behind, the removal is refused — and since nothing checks the return value, the script marches on to branch -d, which refuses in turn because the branch looks unmerged. What accumulates is a quiet pile of failures.
Here it is rewritten to stop where it should.
#!/bin/bash
set -u
FAILED=()
for task in "test-coverage" "api-validation" "docs-update"; do
worktree_path="$WORKTREES_DIR/$task"
if [ ! -d "$worktree_path" ]; then
echo "Skipping: $worktree_path not found"
continue
fi
# Catch what the agent left behind, before touching main
if [ -n "$(git -C "$worktree_path" status --porcelain)" ]; then
echo "Uncommitted changes remain in $task"
FAILED+=("$task (uncommitted)")
continue
fi
branch=$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD)
echo "Merging $branch ..."
if ! git -C "$PROJECT_DIR" merge --no-ff "$branch" \
-m "feat: agent/$task auto-generated changes"; then
# Never carry a half-finished merge into the next iteration
git -C "$PROJECT_DIR" merge --abort 2>/dev/null
echo "Conflict — holding $branch for manual review"
FAILED+=("$task (conflict)")
continue
fi
git -C "$PROJECT_DIR" worktree remove "$worktree_path"
git -C "$PROJECT_DIR" branch -d "$branch"
done
if [ ${#FAILED[@]} -gt 0 ]; then
echo "--- Needs manual attention ---"
printf ' %s\n' "${FAILED[@]}"
exit 1
fiThree things carry the weight. Check status --porcelain before merging. Always merge --abort on failure before continuing. And collect the held-back tasks into an array so they print together at the end.
That third one is quieter than it sounds. With parallel work, losing track of which task failed is itself the damage — and two lines at the bottom of the log changed how long my morning review took.
I deliberately left set -e out. Here I want the remaining merges to proceed after one fails, and set -e fights that. Choosing the stopping points by hand reads better in this kind of loop.
For the next step past worktrees — leasing whole runtime environments, port ranges and database schemas included, one per agent — see Leasing Runtime Environments to Parallel Agents.
Monitoring, Error Handling, and Recovery
Background Agents are designed to run unattended, but "set it and forget it" isn't the full story — especially for long-running tasks. Knowing when and how to check in is what separates reliable results from occasional disappointments.
Effective Checkpoint Usage
Antigravity saves periodic checkpoints during execution. When a task fails mid-way, you can resume from the most recent checkpoint rather than starting over.
My recommended check-in schedule:
- 5–10 minutes after launch: Confirm the agent understood the task correctly
- Midpoint: Verify the approach aligns with your intent
- At completion: Confirm all stated completion criteria are satisfied
If the early check-in shows the agent heading in the wrong direction, it's almost always faster to cancel and relaunch with a clearer specification than to let it continue.
Common Failure Patterns and Fixes
Pattern 1: Context starvation — agent goes in circles
Symptom: No meaningful progress after a long runtime. Agent appears to be retrying the same approach repeatedly.
Root cause: Insufficient project context. The agent is discovering constraints by trial and error.
Fix: Improve AGENTS.md with specific notes about the area being worked on. Break the task into a smaller scope. Add a Knowledge Item with relevant architectural notes.
Pattern 2: Existing tests break
Symptom: The target code improved, but existing tests now fail.
Root cause: "Existing tests must pass" was not explicitly stated as a completion criterion.
Fix: Always include "all existing tests pass without modification" as a completion criterion. Instruct the agent to run the full test suite before starting work.
Pattern 3: TypeScript errors left in place
Symptom: Implementation works at runtime, but type errors remain.
Root cause: Type checking was not included in the completion criteria.
Fix: Always include npx tsc --noEmit shows zero errors as a required completion criterion.
Pattern 4: Unintended file modifications
Symptom: Files you didn't ask about were modified.
Root cause: Agent made "helpful" improvements beyond the task scope.
Fix: Explicitly list files and directories that must not be touched in the Constraints section.
I Had the CI/CD Entry Point Wrong
This is the section I rewrote most heavily.
The earlier version of this article showed GitHub Actions posting to https://api.antigravity.google/v1/agents/background. That host does not exist. If you copied the snippet, it failed at DNS resolution. I'm sorry about that.
Going back to the primary documentation, the way to launch a Background Agent from a pipeline is the Gemini API Interactions API. Everything below follows that spec.
The minimum viable launch-and-wait
The agent identifier is antigravity-preview-05-2026, and asynchronous execution is requested with a background flag in the body. Note that the Api-Revision header appears specifically in the background-execution examples.
# 1. Start the interaction in the background
RESPONSE=$(curl -s -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Api-Revision: 2026-05-20" \
-d '{
"agent": "antigravity-preview-05-2026",
"input": "Run a complex analysis on the repository.",
"environment": "remote",
"background": true
}')
INTERACTION_ID=$(echo "$RESPONSE" | jq -r '.id')
# 2. Poll until status is completed or failed
curl -s -X GET "https://generativelanguage.googleapis.com/v1beta/interactions/$INTERACTION_ID" \
-H "x-goog-api-key: $GEMINI_API_KEY"If you need to stop a run, POST .../v1beta/interactions/INTERACTION_ID:cancel exists. Wiring that in early is what keeps a timed-out job from quietly burning through a budget.
The structured fields I assumed existed
The broken hostname was not the worst part of the old version. The worse part was that I invented the shape of the request body — keys like branch, constraints, create_pr, pr_base, all written as though they were real.
Here is what the documentation actually supports.
| What you want | How it is expressed |
|---|---|
| Choose the agent | agent in the body (antigravity-preview-05-2026) |
| Run asynchronously | background: true (store defaults to on) |
| Execution environment | environment — either "remote" or a full EnvironmentConfig |
| Target repository / branch | No dedicated field. Say it in input, or mount it via environment sources and network rules |
| Open a pull request | No dedicated field. Ask for it in input and allow api.github.com in the network rules |
| Cap token usage | agent_config.max_total_tokens (returns status: "incomplete" when hit) |
In other words, the context you want to pass from CI lives in the natural-language input. Building a clever parameter object in YAML was the wrong instinct; building a good prompt is the right one.
The rewritten workflow
Same intent as before — find under-covered files, ask for tests — but this one actually reaches a server.
# .github/workflows/auto-test-generation.yml
name: Auto Test Generation
on:
pull_request:
types: [opened]
paths:
- 'src/components/**'
- 'src/api/**'
jobs:
generate-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- name: Check coverage
id: coverage
run: |
npx vitest run --coverage --reporter=json 2>/dev/null || true
LOW=$(node -e "
const r = require('./coverage/coverage-summary.json');
const files = Object.entries(r)
.filter(([f, d]) => f !== 'total' && d.lines.pct < 70)
.map(([f]) => f)
.slice(0, 5);
console.log(files.join(','));
")
echo "files=$LOW" >> "$GITHUB_OUTPUT"
- name: Ask the agent to add tests
if: steps.coverage.outputs.files != ''
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
run: |
# Everything goes into input — there are no branch/constraints keys
PROMPT=$(cat <<PROMPT_END
Clone ${{ github.server_url }}/${{ github.repository }} and check out ${{ github.head_ref }}.
Raise line coverage above 80% for these files: ${{ steps.coverage.outputs.files }}.
Use vitest. Do not modify existing tests. Keep \`npx tsc --noEmit\` clean.
Open a pull request with the result.
PROMPT_END
)
RESPONSE=$(jq -n \
--arg agent "antigravity-preview-05-2026" \
--arg input "$PROMPT" \
'{agent: $agent, input: $input, environment: "remote", background: true}' \
| curl -s -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Api-Revision: 2026-05-20" \
-d @-)
ID=$(echo "$RESPONSE" | jq -r '.id // empty')
if [ -z "$ID" ]; then
echo "Launch failed: $RESPONSE"
exit 1
fi
echo "interaction=$ID" >> "$GITHUB_OUTPUT"Building the body with jq -n is not a style preference. The old version concatenated strings — -d "{ \"task\": \"...$FILES\" }" — which breaks the moment a filename contains a quote or a newline. A malformed body comes back as a 400, but in CI logs it reads as "the agent didn't do anything." I lost most of a day to that distinction.
The exit 1 on an empty ID is there for the same reason. A launch that fails while the job stays green produces work you believe you requested and never receive.
Don't fire and forget entirely
Posting from CI without waiting for the result is correct. Posting without recording anything is not. I now drop the interaction ID into a PR comment:
gh pr comment "${{ github.event.pull_request.number }}" \
--body "Requested test coverage work. interaction: \`$ID\` (poll the interactions API for status)"One line, but it's the difference between a pull request someone can trace and one nobody can explain a week later.
Once the request text itself starts growing, the history-folding approach in Designing a Context Compression Sub-Agent in AgentKit 2.0 becomes the thing that keeps it manageable.
Cost Is Measured in Tokens, Not "Credits"
The earlier version of this section talked about credit consumption. The intuition was fine; the vocabulary was wrong, and wrong vocabulary produces wrong estimates.
The Antigravity agent is pay-as-you-go through the Interactions API, billed on the underlying Gemini model tokens plus the tools the agent uses. A standard chat request produces one output. A single Antigravity interaction triggers an autonomous loop of reasoning, tool execution, code running, and file management — which is what changes the shape of the bill.
Documented estimates by task category
These figures come from the official documentation. As a starting baseline they served me better than my own impressions did.
| Task category | Input tokens | Output tokens | Typical cost |
|---|---|---|---|
| Research & information synthesis | 100k–500k | 10k–40k | $0.30–$1.00 |
| Document & content generation | 100k–500k | 15k–50k | $0.30–$1.30 |
| Process & system design | 100k–400k | 10k–30k | $0.25–$0.80 |
| Data processing & analysis | 300k–3M | 30k–150k | $0.70–$3.25 |
Typically 50–70% of input tokens are cached. On the other end, complex agentic workflows with many tool calls can accumulate three to five million tokens in a single interaction, reaching roughly $5.
Environment compute — CPU, memory, sandbox execution — is not billed during the preview period. That's a time-limited condition, so it's worth not baking into long-term assumptions.
Set the ceiling as a parameter, not a hope
The old version claimed rate limits shift by time of day and recommended running important work overnight. That was my impression with nothing behind it. I'm retracting it.
There is a documented mechanism instead. Passing max_total_tokens inside agent_config caps the total tokens — input, output, and thinking — that an interaction may consume. Cached tokens don't count toward the limit.
{
"agent": "antigravity-preview-05-2026",
"input": "Refactor src/utils/ for type safety.",
"environment": "remote",
"background": true,
"agent_config": {
"type": "antigravity",
"max_total_tokens": 1500000
}
}An interaction that hits the ceiling stops and returns status: "incomplete" — neither completed nor failed. That detail matters more than it looks. A polling loop that only branches on those two values will never see incomplete, and your CI job waits forever.
case "$STATUS" in
completed) echo "done" ;;
failed) echo "failed"; exit 1 ;;
incomplete) echo "stopped at the token ceiling — split the task and retry"; exit 1 ;;
*) sleep 15 ;; # in_progress / requires_action
esacThe limit is best-effort, so it isn't an accounting guarantee. I treat it as a circuit breaker against runaway loops, nothing more.
Why vague requests cost more
For the same goal, a well-specified task versus a vague one feels like a 2–3x difference in consumption. I haven't measured that rigorously, so take the number loosely — but the mechanism is clear enough. It's retries.
Without written completion criteria, the agent can't tell whether it's done, so it cycles through checking and adjusting. Every cycle is tokens. That's why adding a "common errors and fixes" section to AGENTS.md is the highest-return edit available: it removes the cycle before it starts.
For anything that finishes in under ten minutes, the inline agent is simply lighter. Reserving Background Agent for work that is long, unattended, and describable in completion criteria is the split that helped most.
Real Production Use Cases
The most convincing evidence for any technique is seeing it work in practice. Here are the Background Agent workflows that have become a regular part of my development routine.
Use Case 1: Overnight Technical Debt Reduction
Launch a Background Agent before sleep, wake up to completed work:
## Task: Improve type safety in src/utils/
### Goal
For all files in src/utils/, achieve the following:
- Zero `any` type usage
- Explicit type annotations on all functions
- JSDoc comments on all exported functions and types
### Completion Criteria
1. `npx tsc --noEmit` shows zero errors
2. `grep -r ": any" src/utils/` returns no results
3. All existing tests pass
4. Every exported function has @param and @returns JSDoc
### Out of Scope
- src/utils/legacy/ (scheduled for separate migration)
- Test mock filesUse Case 2: Rapid Prototyping New Features
Use Background Agent to generate an implementation scaffold, then review and refine:
## Task: Initial implementation of notification settings page
### Goal
Create a working implementation of /settings/notifications.
Reference: Mockup.png (attached as Knowledge Item)
### Implementation Scope
- NotificationSettings component
- State management with Zustand store
- Toggle UI for 4 notification types: email, push, in-app, digest
### Deliberately Out of Scope (implement later)
- Actual notification delivery logic (backend not ready)
- Animations
- Mobile-specific layout
### Completion Criteria
1. Page renders without errors
2. Zero TypeScript errors
3. Toggle state persists to Zustand store
4. Basic accessibility attributes present (aria-label, role)Getting a working scaffold from Background Agent and then refining it yourself is consistently faster than building from scratch — you're editing rather than authoring.
Moving Forward
Using Background Agent effectively is fundamentally about clear communication. The agents that "just work" are the ones where the specification left no room for ambiguity about what success looks like.
The most useful next step is to find one task in your current development backlog that meets the criteria: completable in under 3 hours, testable completion conditions, no dependency on your running local environment. Write a specification using the template from this article, and run it.
The feedback loop from that first well-specified task will tell you more than anything else here could.
Advanced AGENTS.md Patterns for Large Codebases
Once your project grows beyond a certain size, a single root-level AGENTS.md starts to feel insufficient. Here are the patterns I've developed for keeping agent context accurate at scale.
The Layered Context Architecture
The most effective setup I've found for medium-to-large projects uses three layers of context files:
Layer 1 — Project root AGENTS.md: Global constraints that apply to every agent session. Technical stack, forbidden patterns, security-sensitive files, and CI/CD conventions go here. This file should rarely change.
Layer 2 — Domain AGENTS.md: Placed in major subsystem directories (src/api/, src/components/, src/workers/). Contains domain-specific rules that the root file shouldn't need to know about. For example, the API domain file might specify authentication patterns and rate limit handling conventions without cluttering the global file.
Layer 3 — AGENTS.md in task specification: Inline context that's specific to a single task and won't apply broadly. Include this in the task description itself rather than polluting permanent context files.
The key principle is keeping each layer focused. An AGENTS.md that tries to document everything becomes a document nobody reads.
Keeping AGENTS.md Accurate Over Time
Context files that become stale are worse than no context at all — they actively mislead agents. I've started treating AGENTS.md updates as part of the definition of done for any significant refactoring.
A lightweight convention that works well: whenever you make a change that would have saved you time if it had been in AGENTS.md, add it to AGENTS.md in the same commit. This keeps the file accurate without requiring dedicated "documentation sprints."
What Makes a Good Knowledge Item
When Background Agent supports Knowledge Items — additional files you attach to provide context — the selection matters. I've found these consistently valuable:
- ERD or data model diagram: Agents make significantly better database-adjacent decisions when they can see the schema relationships
- Architecture decision records (ADRs): Especially decisions that seem counterintuitive. "We don't use X even though it would seem natural because Y" is exactly the kind of context agents can't infer from code alone
- The last failing test: When you're asking an agent to fix a bug, attaching the test that demonstrates the failure gives it a concrete success condition from the start
- A working example from elsewhere in the codebase: "Make this look like that" with a concrete reference is more reliable than describing the pattern abstractly
Multi-Agent Orchestration Patterns
As you get comfortable running individual Background Agents, you'll naturally start thinking about how to coordinate multiple agents working toward a shared goal. This section covers the orchestration patterns that have proven most reliable.
The Fan-Out / Fan-In Pattern
This is the most common multi-agent pattern and the easiest to reason about:
#!/bin/bash
# fan-out-agents.sh — launch multiple specialized agents, collect results
PROJECT_DIR="$HOME/myapp"
WORKTREES_DIR="$HOME/myapp-worktrees"
RESULTS_DIR="$HOME/myapp-agent-results"
mkdir -p "$RESULTS_DIR"
# Define parallel workstreams
declare -A WORKSTREAMS=(
["tests"]="Add comprehensive tests to src/components/checkout/"
["types"]="Strengthen TypeScript types in src/types/checkout.ts"
["docs"]="Write JSDoc for all exported functions in src/api/checkout/"
)
# Fan out — launch all agents simultaneously
for stream in "${!WORKSTREAMS[@]}"; do
branch="agent/${stream}-checkout-$(date +%Y%m%d)"
worktree="$WORKTREES_DIR/$stream"
# Create isolated worktree
git -C "$PROJECT_DIR" worktree add -b "$branch" "$worktree" main
# Record task start
echo "$(date -Iseconds): Started $stream" >> "$RESULTS_DIR/timeline.log"
# Launch agent (non-blocking — each runs independently)
echo "🚀 Launched agent: $stream"
echo " Task: ${WORKSTREAMS[$stream]}"
echo " Branch: $branch"
done
echo ""
echo "All agents launched. Monitor progress in Antigravity UI."
echo "When all complete, run: ./merge-results.sh"The fan-out pattern works well because each agent has a clearly scoped task with no cross-dependencies. The merge step happens only after all agents finish, making it easy to review results holistically before integrating anything.
The Sequential Dependency Pattern
When tasks have dependencies, you need sequential orchestration rather than parallel:
#!/bin/bash
# sequential-agents.sh — run agents in dependency order
run_agent_and_wait() {
local task="$1"
local branch="$2"
local timeout_minutes="${3:-60}"
echo "▶️ Starting: $task"
# Launch agent (implementation depends on Antigravity CLI availability)
antigravity agent run \
--task "$task" \
--branch "$branch" \
--wait \
--timeout "${timeout_minutes}m" \
2>&1 | tee -a "$RESULTS_DIR/agent.log"
local exit_code=$?
if [ $exit_code -ne 0 ]; then
echo "❌ Agent failed for task: $task"
echo " Check $RESULTS_DIR/agent.log for details"
exit 1
fi
echo "✅ Completed: $task"
}
# Step 1: Generate types first (other steps depend on these)
run_agent_and_wait \
"Create TypeScript interfaces for the new payment flow in src/types/payment.ts" \
"agent/payment-types" \
30
# Step 2: Generate API client based on the new types
run_agent_and_wait \
"Implement API client functions in src/api/payment.ts using the interfaces defined in src/types/payment.ts" \
"agent/payment-api" \
45
# Step 3: Generate UI components using the API client
run_agent_and_wait \
"Create PaymentForm component in src/components/PaymentForm.tsx using the payment API client from src/api/payment.ts" \
"agent/payment-ui" \
60
echo "🎉 All sequential agents completed successfully"The critical discipline here is making each agent's task self-contained within its scope, even when the output depends on the previous step. Include the upstream output as explicit context in each downstream task specification.
Handling Partial Failures
Real orchestration needs to handle the case where some agents succeed and others don't:
# merge-with-failure-handling.sh
FAILED_STREAMS=()
SUCCEEDED_STREAMS=()
for stream in "${!WORKSTREAMS[@]}"; do
worktree="$WORKTREES_DIR/$stream"
branch=$(git -C "$worktree" rev-parse --abbrev-ref HEAD 2>/dev/null)
if [ -z "$branch" ]; then
echo "⚠️ No branch found for $stream — skipping"
FAILED_STREAMS+=("$stream")
continue
fi
# Run validation before merging
cd "$worktree"
if ! npx tsc --noEmit 2>/dev/null; then
echo "❌ TypeScript errors in $stream — not merging"
FAILED_STREAMS+=("$stream")
continue
fi
if ! npx vitest run 2>/dev/null; then
echo "❌ Tests failing in $stream — not merging"
FAILED_STREAMS+=("$stream")
continue
fi
# Safe to merge
git -C "$PROJECT_DIR" merge --no-ff "$branch" \
-m "feat(agent): $stream changes"
SUCCEEDED_STREAMS+=("$stream")
done
echo ""
echo "=== Results ==="
echo "✅ Merged: ${SUCCEEDED_STREAMS[*]}"
echo "❌ Skipped: ${FAILED_STREAMS[*]}"
if [ ${#FAILED_STREAMS[@]} -gt 0 ]; then
echo ""
echo "Failed streams require manual review."
echo "Worktrees preserved at: $WORKTREES_DIR"
fiFor broader multi-agent orchestration strategies, the Orchestrating a Team of Specialist Agents Solo covers additional coordination patterns.
Building a Background Agent Habit Loop
The technical patterns in this guide only deliver value if you actually use them. Here's the habit structure I've found makes Background Agent a natural part of the development day rather than something you remember exists every few weeks.
The Daily Delegation Checklist
At the start of each day, I scan my task list for Background Agent candidates using these questions:
- Is there a task here that I'm context-switching into and out of? (tests, documentation, type improvements)
- Is there something I know needs to be done but am avoiding because it's tedious? (great for delegation)
- Is there a "while I sleep" opportunity — something that could run overnight without my involvement?
One or two tasks per day delegated to Background Agent adds up quickly. Over a month, that's potentially 20–40 hours of parallel work that didn't compete for your focus.
The Specification Investment Mindset
The 10–15 minutes spent writing a good task specification is not overhead — it's the work itself. The clarity you develop when writing the specification often reveals ambiguities in your own thinking about the task. Agents fail for the same reasons human team members fail: unclear requirements, unstated constraints, vague success criteria.
Treat Background Agent task specifications the way you'd treat a ticket written for a strong engineer who can't ask follow-up questions. That mental model produces specifications that work.
If you've read this far, the most useful thing you can do now is open your current project, find one task that fits the criteria, and write a specification using the template from the Task Design section. The difference between Background Agent as "occasionally useful" and Background Agent as "changes how I develop" comes down to whether you build the habit of reaching for it. Start with one well-specified task. See how it goes. Adjust and iterate.