Post

Orchestrating Multi-Agent Teams with Subagents and Worktrees in Antigravity 2.0

This article is part of the Antigravity Engineering Series.

Picture a refactoring task that touches 47 files across 3 modules. A single agent starts at file 1. By file 30, its context window is saturated with the memory of files 1–29 — code it no longer needs but can’t discard. Coherence degrades. The agent begins making contradictory changes. By file 40, it’s undoing work it did at file 15.

This is the context pollution problem, and it’s the primary failure mode of single-agent architectures on large tasks. If you’ve ever watched a monolithic application degrade under load because every request shares the same heap, you’ll recognize the pattern. The solution there wasn’t more RAM — it was decomposition into independent services with their own address spaces.

The same principle applies here. Antigravity 2.0’s subagent system lets the parent agent spawn concurrent child agents, each with an independent context window, optional Git worktree isolation, and a defined role. The Antigravity SDK extends this programmatically — building multi-agent teams with custom roles, tool restrictions, and communication patterns. The architecture looks remarkably like a well-designed service mesh: independent workers, a coordinator, and clear boundaries.


The Context Pollution Problem

Single-agent coherence degrades predictably with task size — a curve that should look familiar to anyone who has benchmarked monolithic vs. distributed systems:

xychart-beta
    title "Agent Coherence vs. Files Modified"
    x-axis ["1-10", "10-20", "20-40", "40+"]
    y-axis "Coherence" 0 --> 100
    bar [95, 65, 30, 10]
Files ModifiedCoherenceFailure Mode
1–10🟢 HighRarely fails
10–20🟡 MediumOccasional inconsistencies
20–40🟠 LowContradicts earlier changes
40+🔴 CriticalUndoes own work

The fix is structural: decompose the task into scoped subtasks, each handled by an agent that only knows about its piece. The parent agent coordinates. The child agents execute. Nobody’s context window holds 47 files of accumulated state. This is the scatter-gather pattern applied to agent architectures.


Subagent Architecture (Antigravity 2.0)

Built-In Subagent Types

Antigravity ships three built-in subagent types, each with a clearly defined capability boundary:

TypePurposeCapabilities
researchCodebase research, navigation, explorationRead-only. Cannot write files or run commands.
browserInteractive browser tasksOperates sandboxed Chrome via /browser command.
selfClone of the parent agentSame system prompt, same toolset as the caller.

Spawning Subagents

The parent agent uses invoke_subagent to spawn concurrent sessions:

1
2
3
4
5
6
7
8
9
10
11
12
Parent agent receives:
  "Refactor the payment module to use the new billing API"

Parent agent reasons:
  "This touches src/billing/, src/checkout/, and src/reports/.
   I'll spawn three subagents — one per module — working in 
   parallel on isolated worktrees."

Parent agent calls invoke_subagent with:
  Subagent 1: { Role: "Billing Module Refactor", Workspace: "worktree" }
  Subagent 2: { Role: "Checkout Module Refactor", Workspace: "worktree" }
  Subagent 3: { Role: "Reports Module Refactor", Workspace: "worktree" }

Each subagent gets:

  • Independent context window — starts clean, no inherited conversation history
  • Optional Git worktree — an isolated filesystem copy of the repo, so agents can’t stomp on each other’s changes
  • Inherited permissions — the parent’s Allow/Deny/Ask rules carry over automatically
  • The same model — subagents use the parent’s configured model

Worktree Isolation

When a subagent specifies Workspace: "worktree", Antigravity creates an isolated Git worktree. This is the agent equivalent of process isolation — each worker operates in its own address space:

---
title: "Worktree Isolation. Each subagent gets its own Git worktree, preventing file conflicts between concurrent agents."
---
flowchart LR
    subgraph Main["Main Workspace (Parent)"]
        direction TB
        M1["src/billing/"]
        M2["src/checkout/"]
        M3["src/reports/"]
    end

    subgraph WT1["Worktree 1 (Subagent 1)"]
        direction TB
        W1a["src/billing/ ✏️"]
        W1b["src/checkout/"]
        W1c["src/reports/"]
    end

    subgraph WT2["Worktree 2 (Subagent 2)"]
        direction TB
        W2a["src/billing/"]
        W2b["src/checkout/ ✏️"]
        W2c["src/reports/"]
    end

    Main -.->|"git worktree add"| WT1
    Main -.->|"git worktree add"| WT2

    style Main fill:#0f3460,stroke:#e94560,color:#eee
    style WT1 fill:#1a1a2e,stroke:#34a853,color:#eee
    style WT2 fill:#1a1a2e,stroke:#f9a825,color:#eee
    style W1a fill:#34a853,stroke:#34a853,color:#fff
    style W2b fill:#f9a825,stroke:#f9a825,color:#000

No shared filesystem state. No merge conflicts during work. The parent integrates the results after all subagents complete — much like a scatter-gather endpoint that collects responses from parallel workers and produces a single aggregated result.


Custom Subagents with define_subagent

The built-in types cover common patterns. For specialized work, agents can define custom subagent types at runtime using define_subagent — think of it as registering a new service type in a service registry:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
The parent agent calls define_subagent:
  name: "migration-specialist"
  description: "Specializes in API migration with test verification"
  system_prompt: |
    You are a migration specialist. Your job is to:
    1. Find all calls to the deprecated API in your assigned module
    2. Migrate each call to the new API signature
    3. Update any test mocks that reference the old API
    4. Run the module's tests: npm test -- --testPathPattern=<module>
    5. Report: files changed, tests passed/failed, any issues
    
    Rules:
    - Never modify files outside your assigned module
    - If a test fails, fix the test — don't revert the migration
    - If you encounter an ambiguous case, report it and move on
  enable_write_tools: true
  enable_mcp_tools: false
  enable_subagent_tools: false

Once defined, the custom subagent can be invoked repeatedly:

1
2
3
4
5
6
7
8
9
10
11
Parent: invoke_subagent(
  TypeName: "migration-specialist",
  Prompt: "Migrate legacy.createUser() in src/billing/",
  Workspace: "worktree"
)

Parent: invoke_subagent(
  TypeName: "migration-specialist",
  Prompt: "Migrate legacy.createUser() in src/checkout/",
  Workspace: "worktree"
)

Tool Restrictions

Custom subagents have fine-grained tool control — the principle of least privilege applied to agent capabilities:

SettingEffect
enable_write_tools: trueCan edit files and run terminal commands
enable_write_tools: falseRead-only — can only search and view
enable_mcp_tools: trueCan use connected MCP servers
enable_subagent_tools: trueCan spawn its own sub-subagents (up to 10 levels deep)

Inter-Agent Communication

Agents communicate using send_message with unique agent IDs — asynchronous point-to-point messaging:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Subagent 1 (billing migration) finishes:
  send_message(
    Recipient: <parent-agent-id>,
    Message: "Migration complete. 12 calls migrated in 5 files. 
              All tests passing. Branch: worktree-1/auto/migrate-billing"
  )

Subagent 2 (checkout migration) hits an issue:
  send_message(
    Recipient: <parent-agent-id>,
    Message: "Found ambiguous case in src/checkout/express-checkout.ts:42.
              The legacy.createUser() call passes a callback as the 4th arg.
              The new API doesn't support callbacks. Need guidance."
  )

Key communication features:

  • Flexible routing: Agents can message any active agent, not just their parent
  • Auto-wake: Sending a message to an idle agent re-awakens it
  • Shared transcripts: Agents can read each other’s conversation logs for context

Agent Lifecycle States

---
title: "Agent Lifecycle States. Subagents transition from running to idle, and can be resumed or terminated by the parent."
---
stateDiagram-v2
    [*] --> Running : invoke_subagent
    Running --> Idle : task done
    Running --> Killed : cancel / kill
    Idle --> Running : send_message\n(re-awakened)
    Killed --> [*]

    note right of Running : Actively executing,\ncalling tools
    note right of Idle : Completed task.\nCan be re-awakened.
    note right of Killed : Terminal state.\nWorktrees cleaned up.
  • Running: Actively executing, calling tools
  • Idle: Completed its task, sent results to parent. Can be re-awakened.
  • Killed: Permanently terminated. Worktrees are cleaned up. Transcripts remain readable.

Programmatic Orchestration with the SDK (Python)

The Antigravity SDK lets you build multi-agent systems programmatically. Notice what’s not in this code: no explicit “spawn a research subagent,” no “define a migration-specialist,” no manual phase orchestration. You set the boundary conditions and state the goal. The agent handles the decomposition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# multi_agent_refactor.py
# Gives the agent a high-level refactoring goal and lets it
# autonomously decide how to decompose, delegate, and coordinate.

import asyncio
from google.antigravity import Agent, LocalAgentConfig
from google.antigravity.types import CapabilitiesConfig
from google.antigravity.hooks.policy import deny, allow


async def main():
    config = LocalAgentConfig(
        capabilities=CapabilitiesConfig(),
        policies=[
            # Safety boundaries — inherited by any subagents the agent spawns
            deny("run_command", when=lambda a: "rm -rf" in a.get("CommandLine", "")),
            deny("run_command", when=lambda a: "git push origin main" in a.get("CommandLine", "")),
            deny("run_command", when=lambda a: "npm publish" in a.get("CommandLine", "")),
            deny("write_to_file", when=lambda a: ".env" in a.get("TargetFile", "")),
            allow("view_file"),
            allow("grep_search"),
            allow("list_dir"),
            allow("run_command", when=lambda a: a.get("CommandLine", "").startswith("npm test")),
            allow("run_command", when=lambda a: a.get("CommandLine", "").startswith("git ")),
            allow("run_command", when=lambda a: a.get("CommandLine", "").startswith("gh pr create")),
            allow("write_to_file", when=lambda a: "/src/" in a.get("TargetFile", "")),
            allow("write_to_file", when=lambda a: "/tests/" in a.get("TargetFile", "")),
            deny("*"),
        ],
    )

    async with Agent(config) as agent:
        # One high-level goal. The agent decides on its own:
        # - Whether to spawn subagents (and how many)
        # - Whether to use worktrees for isolation
        # - How to coordinate research, execution, and integration
        response = await agent.chat(
            "Migrate all legacy.createUser() calls across the codebase to the "
            "new userService.create() API. The old API uses positional args "
            "(name, email, role), the new one uses an object ({ name, email, "
            "role, createdBy: 'migration' }). Update imports from '@app/legacy' "
            "to '@app/services/user'. Update test mocks. All tests must pass. "
            "Open one PR per module when done."
        )
        async for chunk in response:
            print(chunk, end="", flush=True)


if __name__ == "__main__":
    asyncio.run(main())

On a task this size, the agent will typically:

  1. Spawn a read-only research subagent to map the codebase
  2. Define custom specialist subagents based on what it finds
  3. Spawn them in parallel worktrees to avoid filesystem collisions
  4. Coordinate the results and open PRs

The SDK’s role is to set the safety boundary (policies) and the goal (prompt). The agent handles the orchestration — the same separation of concerns you’d apply between a process manager and the workers it supervises.


Multi-Agent Teamwork (Ultra Plan)

For extremely complex tasks, Antigravity 2.0 offers the /teamwork-preview command (Ultra plan, $200/mo). Instead of manually orchestrating subagents, you define the high-level goal and the platform manages agent coordination:

1
2
3
4
> /teamwork-preview
> Refactor the entire payment platform from the legacy API 
> to the new service layer. Migrate all modules, update tests,
> and open one PR per module.

The platform automatically:

  • Decomposes the task into parallelizable subtasks
  • Spawns coordinated subagents with appropriate roles
  • Manages error recovery and automatic retries
  • Coordinates result integration

What We Built

A multi-agent refactoring system where:

  1. Research subagents scan the codebase read-only — mapping the work without modifying anything
  2. Custom specialist subagents execute scoped tasks in isolated Git worktrees — no filesystem collisions
  3. Inter-agent messaging enables coordination — subagents report results and escalate ambiguous cases
  4. The parent agent integrates — merging worktree branches, running the full test suite, opening a consolidated PR
  5. The SDK enables programmatic orchestration — defining custom agents, spawning parallel tasks, collecting results
  6. Permissions inherit automatically — every subagent respects the parent’s safety boundaries

The agent that tries to refactor 47 files in one context window fails predictably. A team of 5 agents, each handling 10 files with clean context, succeeds. Worktree isolation ensures they can’t interfere with each other. Permission inheritance ensures they can’t escape the safety boundary.

That’s not just parallel execution. It’s the difference between one overwhelmed engineer and a coordinated team — the same lesson the industry learned when it moved from monoliths to microservices, applied to a domain where the workers happen to be language models.


Companion code for this post is available at antigravity-multi-agent-worktrees.

This post is licensed under CC BY 4.0 by the author.