◉ANTIGRAVITY LABJP
Articles/Tips & Best Practices
✦ Tips & Best Practices/2026-03-20Advanced

Antigravity Practical Techniques [Part 2] — Multi-Agent, Production Development & Monetization

Advanced techniques from Antigravity Lab premium articles. Part 2 covers multi-agent orchestration, production app development, custom MCP servers, and SaaS monetization.

Antigravity386practical-techniquesadvanced20multi-agent50production71monetization31part-2premium16

Setup and context — Topics in Part 2

Part 1 covered Antigravity fundamentals. Part 2 focuses on production environment complexity, sophisticated system design, and business strategies. Learn advanced implementation patterns essential for real-world work through specific code examples.


Multi-Agent Orchestration Design

Coordinate multiple agents efficiently with clear design patterns.

Router Pattern (TypeScript)

A "router" manages multiple agents, assigning tasks to optimal agents by type.

Architecture

Request
  ↓
Agent Router (classify)
  ├→ Frontend Agent (UI)
  ├→ Backend Agent (API)
  ├→ Data Agent (DB)
  └→ Infra Agent (DevOps)

Implementation

// lib/agent-router.ts
import type { AgentType, Task, TaskResult } from '@/types';
 
interface AgentConfig {
  name: AgentType;
  keywords: string[];
  filePatterns: RegExp[];
  maxConcurrent: number;
}
 
const AGENT_CONFIGS: AgentConfig[] = [
  {
    name: 'frontend',
    keywords: ['component', 'ui', 'page', 'styled'],
    filePatterns: [/src\/(components|app|pages)\//, /\.tsx?$/],
    maxConcurrent: 2,
  },
  {
    name: 'backend',
    keywords: ['api', 'endpoint', 'database', 'query'],
    filePatterns: [/src\/(api|server|db)\//, /route\.(ts|js)$/],
    maxConcurrent: 3,
  },
  {
    name: 'data',
    keywords: ['migration', 'schema', 'index', 'optimize'],
    filePatterns: [/migrations\//, /schema\.(sql|ts)$/],
    maxConcurrent: 1,
  },
  {
    name: 'infra',
    keywords: ['deploy', 'ci', 'github', 'docker'],
    filePatterns: [/\.github/, /dockerfile/i, /\.env/i],
    maxConcurrent: 1,
  },
];
 
export class AgentRouter {
  private activeAgents: Map<AgentType, number> = new Map();
 
  constructor() {
    AGENT_CONFIGS.forEach(config => {
      this.activeAgents.set(config.name, 0);
    });
  }
 
  routeTask(task: Task): AgentType {
    // Extract keywords from task description
    const taskLower = task.description.toLowerCase();
    const matchedAgents = AGENT_CONFIGS
      .filter(config =>
        config.keywords.some(kw => taskLower.includes(kw)) ||
        config.filePatterns.some(pattern =>
          task.affectedFiles?.some(f => pattern.test(f))
        )
      );
 
    if (matchedAgents.length === 0) {
      // Default to frontend
      return 'frontend';
    }
 
    // Select agent with lowest load
    let selectedAgent = matchedAgents[0];
    for (const agent of matchedAgents) {
      const current = this.activeAgents.get(agent.name) || 0;
      const selected = this.activeAgents.get(selectedAgent.name) || 0;
 
      if (current < selected) {
        selectedAgent = agent;
      }
    }
 
    return selectedAgent.name;
  }
 
  async executeTask(task: Task): Promise<TaskResult> {
    const agentType = this.routeTask(task);
    const current = this.activeAgents.get(agentType) || 0;
 
    // Check connection limits
    const config = AGENT_CONFIGS.find(c => c.name === agentType)!;
    if (current >= config.maxConcurrent) {
      throw new Error(
        `Agent ${agentType} at max capacity (${config.maxConcurrent})`
      );
    }
 
    // Start execution
    this.activeAgents.set(agentType, current + 1);
 
    try {
      const result = await this.sendToAgent(agentType, task);
      return result;
    } finally {
      this.activeAgents.set(agentType, current);
    }
  }
 
  private async sendToAgent(
    agentType: AgentType,
    task: Task
  ): Promise<TaskResult> {
    // Call Antigravity Agent API
    const response = await fetch('/api/agents/execute', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ agentType, task }),
    });
 
    return response.json();
  }
}

Pipeline Pattern (TypeScript)

Execute a series of steps sequentially, each using a different agent.

Example: SaaS Deploy Pipeline

// lib/deployment-pipeline.ts
interface PipelineStep {
  name: string;
  agent: AgentType;
  dependencies: string[];
  execute: (context: PipelineContext) => Promise<void>;
}
 
interface PipelineContext {
  commitHash: string;
  environment: 'staging' | 'production';
  artifacts: Map<string, any>;
}
 
export class DeploymentPipeline {
  private steps: PipelineStep[] = [
    {
      name: 'lint',
      agent: 'frontend',
      dependencies: [],
      execute: async (ctx) => {
        console.log('Linting code...');
        // Frontend Agent runs ESLint/Prettier
        ctx.artifacts.set('lint-report', { status: 'pass' });
      },
    },
    {
      name: 'test',
      agent: 'frontend',
      dependencies: ['lint'],
      execute: async (ctx) => {
        console.log('Running tests...');
        // Frontend Agent runs Jest
        ctx.artifacts.set('test-coverage', { percent: 87 });
      },
    },
    {
      name: 'build-frontend',
      agent: 'frontend',
      dependencies: ['test'],
      execute: async (ctx) => {
        console.log('Building frontend...');
        // Frontend Agent builds Next.js
        ctx.artifacts.set('frontend-bundle', { size: '2.3MB' });
      },
    },
    {
      name: 'build-backend',
      agent: 'backend',
      dependencies: [],
      execute: async (ctx) => {
        console.log('Building backend...');
        // Backend Agent builds Node.js
        ctx.artifacts.set('backend-bundle', { size: '1.8MB' });
      },
    },
    {
      name: 'db-migration',
      agent: 'data',
      dependencies: [],
      execute: async (ctx) => {
        console.log('Running database migrations...');
        // Data Agent executes DB migration
        ctx.artifacts.set('migration-status', { applied: 3 });
      },
    },
    {
      name: 'deploy',
      agent: 'infra',
      dependencies: [
        'build-frontend',
        'build-backend',
        'db-migration',
      ],
      execute: async (ctx) => {
        console.log('Deploying to cloud...');
        // Infra Agent deploys to Vercel/Render
        ctx.artifacts.set('deploy-status', {
          url: 'https://staging.app.com',
          time: '3m 45s',
        });
      },
    },
    {
      name: 'e2e-test',
      agent: 'frontend',
      dependencies: ['deploy'],
      execute: async (ctx) => {
        console.log('Running E2E tests...');
        // Frontend Agent runs Playwright
        ctx.artifacts.set('e2e-result', { passed: 45, failed: 0 });
      },
    },
  ];
 
  async run(commitHash: string, environment: 'staging' | 'production') {
    const context: PipelineContext = {
      commitHash,
      environment,
      artifacts: new Map(),
    };
 
    const completed = new Set<string>();
 
    for (const step of this.steps) {
      // Wait for dependencies
      while (!step.dependencies.every(dep => completed.has(dep))) {
        await new Promise(r => setTimeout(r, 100));
      }
 
      try {
        await step.execute(context);
        completed.add(step.name);
        console.log(`✓ ${step.name} completed by ${step.agent}`);
      } catch (error) {
        console.error(`✗ ${step.name} failed:`, error);
        throw error;
      }
    }
 
    return context;
  }
}

Browser Sub-Agent E2E Test Automation

Auto-generate and run complex E2E tests with Playwright + Browser Sub-Agent.

// tests/e2e/checkout.spec.ts
import { test, expect } from '@playwright/test';
 
test('complete checkout flow', async ({ page }) => {
  // Auto-generated by Browser Sub-Agent
 
  // Step 1: Login
  await page.goto('/login');
  await page.fill('input[name="email"]', 'user@example.com');
  await page.fill('input[name="password"]', 'password123');
  await page.click('button[type="submit"]');
  await page.waitForNavigation();
 
  // Step 2: Add to cart
  await page.goto('/products');
  await page.click('text=Add to Cart');
  await expect(page).toHaveURL('/cart');
 
  // Step 3: Checkout
  await page.click('button:has-text("Proceed to Checkout")');
 
  // Step 4: Enter shipping info
  await page.fill('input[name="address"]', '123 Main St');
  await page.select('select[name="country"]', 'US');
 
  // Step 5: Payment
  await page.click('text=Pay with Stripe');
  // Auto-fill card in Stripe iframe
 
  // Step 6: Confirm
  await expect(page).toHaveURL('/order-confirmation');
  await expect(page).toContainText('Thank you for your order');
});

Production App Development

SwiftUI + CloudKit iCloud Sync App

iOS app with automatic iCloud synchronization.

// Models/Note.swift
import Foundation
import CloudKit
 
struct Note: Identifiable {
    let id: UUID
    var title: String
    var content: String
    var modifiedDate: Date
 
    // CloudKit integration
    var cloudKitRecord: CKRecord?
 
    func toCKRecord() -> CKRecord {
        let record = CKRecord(recordType: "Note", recordID: CKRecordID(recordName: id.uuidString))
        record["title"] = title
        record["content"] = content
        record["modifiedDate"] = modifiedDate
        return record
    }
 
    static func fromCKRecord(_ record: CKRecord) -> Note {
        return Note(
            id: UUID(uuidString: record.recordID.recordName) ?? UUID(),
            title: record["title"] as? String ?? "",
            content: record["content"] as? String ?? "",
            modifiedDate: record["modifiedDate"] as? Date ?? Date(),
            cloudKitRecord: record
        )
    }
}
// ViewModels/NoteViewModel.swift
import CloudKit
import Observation
 
@Observable
class NoteViewModel {
    var notes: [Note] = []
    var isLoading = false
 
    private let database = CKContainer.default().publicCloudDatabase
 
    @MainActor
    func fetchNotes() async {
        isLoading = true
        let predicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "Note", predicate: predicate)
 
        do {
            let records = try await database.records(matching: query)
            notes = records.compactMap { Note.fromCKRecord($0) }
        } catch {
            print("Error fetching notes: \(error)")
        }
        isLoading = false
    }
 
    @MainActor
    func saveNote(_ note: Note) async {
        let record = note.toCKRecord()
 
        do {
            _ = try await database.save(record)
            if let index = notes.firstIndex(where: { $0.id == note.id }) {
                notes[index] = Note.fromCKRecord(record)
            } else {
                notes.append(Note.fromCKRecord(record))
            }
        } catch {
            print("Error saving note: \(error)")
        }
    }
}

Android Jetpack Compose + MVI Architecture

Latest Android best practices implementation.

// mvi/PostIntent.kt
sealed class PostIntent {
    data class LoadPosts(val page: Int) : PostIntent()
    data class RefreshPosts : PostIntent()
    data class DeletePost(val id: String) : PostIntent()
    data class FavoritePost(val id: String) : PostIntent()
}
// mvi/PostViewState.kt
data class PostViewState(
    val posts: List<Post> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null,
    val selectedPost: Post? = null,
)
// mvi/PostViewModel.kt
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.*
 
class PostViewModel(
    private val postRepository: PostRepository
) : ViewModel() {
 
    private val intentFlow = MutableSharedFlow<PostIntent>()
 
    val viewState: StateFlow<PostViewState> = intentFlow
        .scan(PostViewState()) { state, intent ->
            when (intent) {
                is PostIntent.LoadPosts -> {
                    state.copy(isLoading = true)
                }
                is PostIntent.RefreshPosts -> {
                    state.copy(isLoading = true)
                }
                is PostIntent.DeletePost -> {
                    state.copy(
                        posts = state.posts.filter { it.id != intent.id }
                    )
                }
                is PostIntent.FavoritePost -> {
                    state.copy(
                        posts = state.posts.map { post ->
                            if (post.id == intent.id) {
                                post.copy(isFavorite = !post.isFavorite)
                            } else post
                        }
                    )
                }
            }
        }
        .stateIn(
            viewModelScope,
            SharingStarted.WhileSubscribed(5000),
            PostViewState()
        )
 
    fun sendIntent(intent: PostIntent) {
        viewModelScope.launch {
            intentFlow.emit(intent)
        }
    }
}
// ui/PostScreen.kt
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
 
@Composable
fun PostScreen(viewModel: PostViewModel) {
    val state by viewModel.viewState.collectAsState()
 
    LaunchedEffect(Unit) {
        viewModel.sendIntent(PostIntent.LoadPosts(1))
    }
 
    when {
        state.isLoading -> {
            CircularProgressIndicator()
        }
        state.error != null -> {
            Text("Error: ${state.error}")
        }
        else -> {
            LazyColumn {
                items(state.posts.size) { index ->
                    PostItem(
                        post = state.posts[index],
                        onDelete = {
                            viewModel.sendIntent(PostIntent.DeletePost(state.posts[index].id))
                        },
                        onFavorite = {
                            viewModel.sendIntent(PostIntent.FavoritePost(state.posts[index].id))
                        }
                    )
                }
            }
        }
    }
}

Cloudflare Workers AI Edge AI App

Low-latency AI via Cloudflare Workers integration.

// src/index.ts (Cloudflare Worker)
import { Hono } from 'hono';
 
const app = new Hono();
 
interface AIRequest {
  prompt: string;
  maxTokens?: number;
}
 
app.post('/api/ai/generate', async (c) => {
  const { prompt, maxTokens = 100 } = await c.req.json<AIRequest>();
 
  // @ts-ignore - Cloudflare Workers AI API
  const response = await c.env.AI.run('@cf/mistral/mistral-7b-instruct-v0.1', {
    prompt,
    max_tokens: maxTokens,
  });
 
  return c.json({
    prompt,
    generated: response.response,
    model: '@cf/mistral/mistral-7b-instruct-v0.1',
  });
});
 
app.post('/api/ai/classify', async (c) => {
  const { text } = await c.req.json();
 
  // Sentiment analysis
  const sentiment = await c.env.AI.run('@cf/huggingface/distilbert-sst-2-en', {
    text,
  });
 
  return c.json({
    text,
    sentiment: sentiment.result,
  });
});
 
export default app;

Custom MCP Server Development

Build custom tools for Antigravity.

TypeScript MCP Server

// mcp-server/src/index.ts
import Anthropic from '@anthropic-ai/sdk';
 
interface Tool {
  name: string;
  description: string;
  inputSchema: {
    type: string;
    properties: Record<string, any>;
    required: string[];
  };
}
 
interface Resource {
  uri: string;
  name: string;
  mimeType: string;
  description: string;
}
 
class CustomMCPServer {
  private tools: Tool[] = [
    {
      name: 'fetch_analytics',
      description: 'Fetch analytics from company dashboard',
      inputSchema: {
        type: 'object',
        properties: {
          metric: {
            type: 'string',
            description: 'Metric: revenue, users, retention',
          },
          period: {
            type: 'string',
            description: 'Period: daily, weekly, monthly',
          },
        },
        required: ['metric', 'period'],
      },
    },
    {
      name: 'list_deployments',
      description: 'List recent cloud deployments',
      inputSchema: {
        type: 'object',
        properties: {
          environment: {
            type: 'string',
            enum: ['staging', 'production'],
          },
          limit: {
            type: 'number',
            description: 'Max deployments to return',
          },
        },
        required: ['environment'],
      },
    },
  ];
 
  private resources: Resource[] = [
    {
      uri: 'internal://company/design-system',
      name: 'Design System',
      mimeType: 'text/markdown',
      description: 'Company design system and components',
    },
    {
      uri: 'internal://company/api-spec',
      name: 'API Specification',
      mimeType: 'application/json',
      description: 'OpenAPI spec for company APIs',
    },
  ];
 
  async listTools(): Promise<Tool[]> {
    return this.tools;
  }
 
  async listResources(): Promise<Resource[]> {
    return this.resources;
  }
 
  async callTool(
    toolName: string,
    params: Record<string, string | number>
  ): Promise<string> {
    switch (toolName) {
      case 'fetch_analytics':
        return this.fetchAnalytics(params);
      case 'list_deployments':
        return this.listDeployments(params);
      default:
        throw new Error(`Unknown tool: ${toolName}`);
    }
  }
 
  private async fetchAnalytics(params: Record<string, string | number>) {
    const { metric, period } = params;
 
    // Call analytics dashboard API
    const response = await fetch('https://analytics.company.com/api/metrics', {
      method: 'POST',
      headers: { Authorization: `Bearer ${process.env.ANALYTICS_API_KEY}` },
      body: JSON.stringify({ metric, period }),
    });
 
    const data = await response.json();
    return JSON.stringify(data);
  }
 
  private async listDeployments(params: Record<string, string | number>) {
    const { environment, limit = 10 } = params;
 
    // Call deploy history API
    const response = await fetch(
      `https://deploy.company.com/api/deployments?env=${environment}&limit=${limit}`,
      {
        headers: { Authorization: `Bearer ${process.env.DEPLOY_API_KEY}` },
      }
    );
 
    const data = await response.json();
    return JSON.stringify(data);
  }
}
 
// Start server
const server = new CustomMCPServer();
 
// REST API endpoints
import express from 'express';
const app = express();
 
app.post('/mcp/tools', async (req, res) => {
  res.json({ tools: await server.listTools() });
});
 
app.post('/mcp/resources', async (req, res) => {
  res.json({ resources: await server.listResources() });
});
 
app.post('/mcp/call', async (req, res) => {
  try {
    const { tool, params } = req.body;
    const result = await server.callTool(tool, params);
    res.json({ result });
  } catch (error) {
    res.status(400).json({ error: (error as Error).message });
  }
});
 
app.listen(3001, () => {
  console.log('MCP Server listening on port 3001');
});

SaaS Monetization Pipeline

Antigravity × Stripe Full-Stack SaaS

// app/api/stripe/create-subscription.ts
import Stripe from 'stripe';
import { supabaseAdmin } from '@/lib/supabase';
 
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
 
export async function POST(req: Request) {
  const { userId, priceId } = await req.json();
 
  // 1. Get or create Stripe customer
  const { data: user } = await supabaseAdmin
    .from('users')
    .select('stripe_customer_id, email')
    .eq('id', userId)
    .single();
 
  let customerId = user?.stripe_customer_id;
 
  if (!customerId) {
    const customer = await stripe.customers.create({
      email: user.email,
      metadata: { userId },
    });
    customerId = customer.id;
 
    await supabaseAdmin
      .from('users')
      .update({ stripe_customer_id: customerId })
      .eq('id', userId);
  }
 
  // 2. Create subscription
  const subscription = await stripe.subscriptions.create({
    customer: customerId,
    items: [{ price: priceId }],
    payment_behavior: 'default_incomplete',
    expand: ['latest_invoice.payment_intent'],
  });
 
  // 3. Record in Supabase
  await supabaseAdmin
    .from('subscriptions')
    .insert({
      user_id: userId,
      stripe_subscription_id: subscription.id,
      status: subscription.status,
      current_period_end: new Date(subscription.current_period_end * 1000),
    });
 
  return new Response(JSON.stringify(subscription), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
}

YouTube Tutorial Video Auto-Production

Antigravity auto-generates YouTube scripts and cutting instructions.

// scripts/generate-tutorial.ts
import { Anthropic } from '@anthropic-ai/sdk';
 
const client = new Anthropic();
 
async function generateYouTubeTutorial(topic: string): Promise<string> {
  const message = await client.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 4000,
    messages: [
      {
        role: 'user',
        content: `
Generate YouTube tutorial video script.
 
Topic: ${topic}
Target: Beginner to intermediate
Duration: 10 minutes
 
Format:
[Scene 1]
Time: 0:00-0:30
Narration: ...
Visual: ...
 
[Scene 2]
...
`,
      },
    ],
  });
 
  return message.content[0].type === 'text' ? message.content[0].text : '';
}
 
// Execution
const script = await generateYouTubeTutorial(
  'Multi-Agent Development with Antigravity'
);
console.log(script);

Kindle Technical Book Efficient Writing

// scripts/generate-ebook.ts
interface EbookChapter {
  title: string;
  sections: {
    heading: string;
    content: string;
  }[];
}
 
async function generateEbookChapter(chapterTopic: string): Promise<EbookChapter> {
  const response = await fetch('/api/ai/generate', {
    method: 'POST',
    body: JSON.stringify({
      prompt: `
Write Kindle technical book chapter.
 
Chapter: ${chapterTopic}
Style: Technical, practical, code examples included
Length: 3000-4000 words
 
Sections:
1. Overview
2. Fundamentals
3. Implementation (with code)
4. Best Practices
5. FAQ
`,
    }),
  });
 
  const data = (await response.json()) as { generated: string };
 
  // Parse Markdown to structured format
  return parseMarkdownToChapter(data.generated);
}
 
function parseMarkdownToChapter(markdown: string): EbookChapter {
  // Markdown → structured data conversion
  return {
    title: 'Chapter Title',
    sections: [],
  };
}

Summary — Related Premium Articles

Part 2 taught production-level development technology with Antigravity.

Next Premium Article Series

  • "Multi-Agent System Design Pattern Collection"
  • "Production iOS/Android App Operations Techniques"
  • "Achieving Rapid SaaS Growth with AI Automation"
  • "Writing Kindle Bestsellers with Antigravity"

Join Antigravity Lab Premium to experience cutting-edge AI-driven development.

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 $10 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
Where should a solo developer sell an agent built on Antigravity? Five channels — marketplace, own site, SaaS embed, consulting-to-product, subscription — compared by take-home share, customer distance and time to first revenue, with Stripe checkout code and a usage-cap check.
✦ Tips2026-10-15
The Images Weren't Broken, My Way of Checking Them Was: Doubting the iframe First, Then Reading currentSrc
A record of misdiagnosing healthy images as unloaded because I audited them through an iframe in a separate tab. In a hidden frame, naturalWidth reads 0 at a rendered width of 0, and lazy images never start. Here are the numbers I reproduced locally, an audit script that reads currentSrc and rendered width on the real page, and the trap of bulk-replacing CSS values with a regex.
✦ Tips2026-09-22
Don't Leave Your Chat Records to the Export Button — Three Routes for the Night It Does Nothing
Three ways to keep a record of your Antigravity conversations when the Export button does nothing at all: how to tell a broken button from a silent success, how to locate the conversation file in your own environment by modification time, and how to decide what to keep before the session ends.