◉ANTIGRAVITY LABJP
Articles/Agents & Manager
◈ Agents & Manager/2026-03-14Advanced

Multi-Agent Orchestration with Antigravity — A Production Implementation Guide

Build production-grade multi-agent systems using Antigravity. Covers orchestrator/worker separation, DAG-based task management, parallel execution, retry logic, and cost optimization with real Python code.

Antigravity377multi-agent50orchestration21AI pipelineproduction71

Why Multi-Agent Architecture?

Single-agent systems hit a ceiling with tasks requiring parallel data collection, large-scale refactoring, or long-form document generation. Multi-agent architecture solves this by decomposing complex work across specialized agents running concurrently.

The system we'll build:

[Orchestrator Agent]
    ├── [Research Agent × 3]     (parallel)
    ├── [Code Review Agent]       (sequential)
    └── [Report Generation Agent] (final aggregation)
ℹ️
You'll need an Antigravity API key. If you haven't obtained one yet, obtain one from the Antigravity settings panel first.

1. Core Design Principles

Orchestrator vs Worker Separation

Orchestrator
  Role: Task decomposition, state management, result aggregation
  Characteristics: Long context window, infrequent calls, high-quality model

Worker Agents
  Role: Execute specific sub-tasks
  Characteristics: Short context, high frequency, parallelizable

Use a powerful model (e.g., claude-opus-4) for the orchestrator and fast/cheap models (e.g., claude-haiku-4-5) for workers to balance cost and latency.


2. Task Graph Engine

# task_graph.py
import asyncio
import json
from typing import Any
from dataclasses import dataclass, field
from enum import Enum
import anthropic
 
class TaskStatus(Enum):
    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    RETRYING = "retrying"
 
@dataclass
class Task:
    id: str
    name: str
    description: str
    dependencies: list[str] = field(default_factory=list)
    status: TaskStatus = TaskStatus.PENDING
    result: Any = None
    error: str | None = None
    retries: int = 0
    max_retries: int = 3
 
class TaskGraph:
    """Directed Acyclic Graph (DAG) for managing task execution order"""
 
    def __init__(self):
        self.tasks: dict[str, Task] = {}
        self.client = anthropic.Anthropic()
 
    def add_task(self, task: Task):
        self.tasks[task.id] = task
 
    def get_ready_tasks(self) -> list[Task]:
        """Return tasks whose dependencies are all complete"""
        ready = []
        for task in self.tasks.values():
            if task.status != TaskStatus.PENDING:
                continue
            deps_done = all(
                self.tasks[dep].status == TaskStatus.COMPLETED
                for dep in task.dependencies
                if dep in self.tasks
            )
            if deps_done:
                ready.append(task)
        return ready
 
    def is_complete(self) -> bool:
        return all(
            t.status in (TaskStatus.COMPLETED, TaskStatus.FAILED)
            for t in self.tasks.values()
        )
 
    def get_context_for_task(self, task: Task) -> dict:
        """Gather completed dependency results as context"""
        return {
            dep_id: self.tasks[dep_id].result
            for dep_id in task.dependencies
            if dep_id in self.tasks and self.tasks[dep_id].result
        }

3. Worker Agent Implementation

# worker_agent.py
import asyncio
import json
from anthropic import AsyncAnthropic
 
class WorkerAgent:
    """Executes a single, well-scoped task"""
 
    def __init__(
        self,
        agent_id: str,
        system_prompt: str,
        model: str = "claude-haiku-4-5-20251001",
        max_tokens: int = 4096,
    ):
        self.agent_id = agent_id
        self.system_prompt = system_prompt
        self.model = model
        self.max_tokens = max_tokens
        self.client = AsyncAnthropic()
        self.tools = []
 
    def add_tool(self, tool_definition: dict):
        self.tools.append(tool_definition)
 
    async def execute(
        self,
        task_description: str,
        context: dict = None,
        timeout: float = 120.0,
    ) -> dict:
        messages = self._build_messages(task_description, context or {})
 
        try:
            response = await asyncio.wait_for(
                self._run_agent_loop(messages),
                timeout=timeout,
            )
            return {"status": "success", "result": response, "agent_id": self.agent_id}
        except asyncio.TimeoutError:
            return {"status": "timeout", "result": None, "agent_id": self.agent_id}
        except Exception as e:
            return {"status": "error", "result": None, "error": str(e), "agent_id": self.agent_id}
 
    def _build_messages(self, task: str, context: dict) -> list:
        context_str = ""
        if context:
            context_str = "\n\n## Context from completed dependencies\n"
            for dep_id, result in context.items():
                context_str += f"\n### {dep_id}\n{json.dumps(result, indent=2)}\n"
 
        return [
            {
                "role": "user",
                "content": f"{context_str}\n\n## Your task\n{task}"
            }
        ]
 
    async def _run_agent_loop(self, messages: list) -> str:
        max_iterations = 10
 
        for _ in range(max_iterations):
            kwargs = {
                "model": self.model,
                "max_tokens": self.max_tokens,
                "system": self.system_prompt,
                "messages": messages,
            }
            if self.tools:
                kwargs["tools"] = self.tools
 
            response = await self.client.messages.create(**kwargs)
            tool_uses = [b for b in response.content if b.type == "tool_use"]
 
            if not tool_uses or response.stop_reason == "end_turn":
                return "\n".join(b.text for b in response.content if hasattr(b, "text"))
 
            messages.append({"role": "assistant", "content": response.content})
            tool_results = await self._execute_tools(tool_uses)
            messages.append({"role": "user", "content": tool_results})
 
        return "Reached max iterations"
 
    async def _execute_tools(self, tool_uses: list) -> list:
        return [
            {
                "type": "tool_result",
                "tool_use_id": tool_use.id,
                "content": f"Tool {tool_use.name} not implemented",
            }
            for tool_use in tool_uses
        ]

4. Parallel Orchestrator

# parallel_orchestrator.py
import asyncio
from task_graph import TaskGraph, Task, TaskStatus
from worker_agent import WorkerAgent
 
class ParallelOrchestrator:
    """Runs tasks in parallel respecting DAG dependency order"""
 
    def __init__(self, max_concurrent: int = 5):
        self.max_concurrent = max_concurrent
        self.semaphore = asyncio.Semaphore(max_concurrent)
        self.graph = TaskGraph()
        self.workers: dict[str, WorkerAgent] = {}
 
    def register_worker(self, task_pattern: str, worker: WorkerAgent):
        self.workers[task_pattern] = worker
 
    def _get_worker_for_task(self, task: Task) -> WorkerAgent:
        for pattern, worker in self.workers.items():
            if pattern in task.name or pattern == "*":
                return worker
        return WorkerAgent(
            agent_id="default",
            system_prompt="You are a general-purpose task execution agent.",
        )
 
    async def execute_task(self, task: Task) -> None:
        async with self.semaphore:
            task.status = TaskStatus.RUNNING
            worker = self._get_worker_for_task(task)
            context = self.graph.get_context_for_task(task)
 
            while task.retries <= task.max_retries:
                if task.retries > 0:
                    task.status = TaskStatus.RETRYING
                    await asyncio.sleep(2 ** task.retries)  # exponential backoff
 
                result = await worker.execute(
                    task_description=task.description,
                    context=context,
                )
 
                if result["status"] == "success":
                    task.result = result["result"]
                    task.status = TaskStatus.COMPLETED
                    print(f"✅ Completed: {task.name}")
                    return
 
                task.retries += 1
                task.error = result.get("error", "Unknown error")
                print(f"⚠️ Failed (attempt {task.retries}/{task.max_retries}): {task.name}")
 
            task.status = TaskStatus.FAILED
            print(f"❌ Permanently failed: {task.name}")
 
    async def run(self) -> dict:
        print(f"🚀 Orchestrator starting: {len(self.graph.tasks)} tasks")
 
        while not self.graph.is_complete():
            ready_tasks = self.graph.get_ready_tasks()
 
            if not ready_tasks:
                running = [t for t in self.graph.tasks.values() if t.status == TaskStatus.RUNNING]
                if not running:
                    print("⛔ Deadlock detected — aborting")
                    break
                await asyncio.sleep(0.1)
                continue
 
            await asyncio.gather(
                *[self.execute_task(task) for task in ready_tasks],
                return_exceptions=True,
            )
 
        return {
            task_id: task.result
            for task_id, task in self.graph.tasks.items()
            if task.status == TaskStatus.COMPLETED
        }

5. Real-World Example: Codebase Analysis Pipeline

# codebase_analysis.py
import asyncio
from parallel_orchestrator import ParallelOrchestrator
from task_graph import Task
from worker_agent import WorkerAgent
 
async def analyze_codebase(repo_path: str) -> str:
    orchestrator = ParallelOrchestrator(max_concurrent=3)
 
    analyzer = WorkerAgent(
        agent_id="code-analyzer",
        system_prompt="""You are a code quality analyst.
Analyze code for quality issues, tech debt, and performance bottlenecks.
Return results as structured JSON.""",
        model="claude-haiku-4-5-20251001",
    )
 
    security_reviewer = WorkerAgent(
        agent_id="security-reviewer",
        system_prompt="""You are a security expert.
Review code from an OWASP Top 10 perspective.
Identify and categorize all security vulnerabilities.""",
        model="claude-haiku-4-5-20251001",
    )
 
    reporter = WorkerAgent(
        agent_id="report-generator",
        system_prompt="""You are a technical writer.
Synthesize analysis results into a prioritized executive summary
with actionable recommendations.""",
        model="claude-sonnet-4-6",  # Higher quality model for final aggregation
    )
 
    orchestrator.register_worker("analyze", analyzer)
    orchestrator.register_worker("security", security_reviewer)
    orchestrator.register_worker("report", reporter)
 
    tasks = [
        Task(
            id="analyze_frontend",
            name="analyze-frontend",
            description=f"Analyze {repo_path}/src/frontend for code quality issues.",
        ),
        Task(
            id="analyze_backend",
            name="analyze-backend",
            description=f"Analyze {repo_path}/src/backend for code quality issues.",
        ),
        Task(
            id="security_check",
            name="security-review",
            description=f"Perform a security audit of {repo_path}.",
        ),
        Task(
            id="final_report",
            name="report-generation",
            description="Aggregate all analysis results into a prioritized improvement report.",
            dependencies=["analyze_frontend", "analyze_backend", "security_check"],
        ),
    ]
 
    for task in tasks:
        orchestrator.graph.add_task(task)
 
    results = await orchestrator.run()
    return results.get("final_report", "Report generation failed")
 
if __name__ == "__main__":
    result = asyncio.run(analyze_codebase("/path/to/your/project"))
    print(result)

6. Production Checklist

"""
Production multi-agent checklist
 
✅ Timeouts
   - Every worker has asyncio.wait_for timeout (default: 120s)
   - Global pipeline timeout to prevent runaway costs
 
✅ Retry Strategy
   - max_retries: 3
   - Exponential backoff: 2^n seconds (2, 4, 8)
   - Idempotent tasks (safe to run multiple times)
 
✅ Cost Management
   - Workers: claude-haiku-4-5 (fast, cheap)
   - Orchestrator: claude-opus-4 (accurate planning)
   - Explicit max_tokens on every call
 
✅ Observability
   - Log task start/end/error with timestamps
   - Track token usage per task
   - Alert on consecutive failures
 
✅ Graceful Degradation
   - Non-critical task failures: continue with None result
   - Critical task failures: abort pipeline and notify
"""

Looking back

Multi-agent systems demand more than stringing agents together — you need DAG-based task management, exponential backoff retries, and thoughtful cost allocation.

The architecture in this guide scales from a 2-agent automation script to 50+ agent pipelines without structural changes. Start small: build a 2-agent pipeline today, validate it end-to-end, then layer in complexity.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◈ Agents & Manager2026-04-27
Five Paths to Sell AI Agents Built on AgentKit 2.0 — How Indie Developers Should Pick a Distribution Channel
AgentKit 2.0 lets a solo developer ship a real, useful AI agent. The hard question is where to sell it. This article walks through five distribution paths — marketplaces, direct sales, SaaS embedding, productized consulting, and subscription agents — with the trade-offs that matter.
◈ Agents & Manager2026-04-14
Build an AI Agent SaaS with Antigravity × AgentKit 2.0 × Stripe — A Complete Implementation Guide for Solo Developers
How to build a subscription AI agent SaaS with AgentKit 2.0, Stripe and Antigravity. Architecture, billing, Webhook idempotency, production deployment — plus the Redis concurrency-slot bug that quietly locks out your heaviest users.
▣ App Dev2026-05-05
Building Serverless AI Pipelines with Antigravity: Cloud Run, Pub/Sub, and BigQuery in Production
A hands-on walkthrough for building production-grade serverless AI processing pipelines with Antigravity, Google Cloud Run, Pub/Sub, and BigQuery. Covers event-driven design, dead-letter queues, retry strategies, idempotency, and automated result storage — all with working code.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links