ANTIGRAVITY LABJP
Articles/AI Tools
AI Tools/2026-04-19Advanced

Running Gemma 4 Locally with Antigravity: The Complete Production Setup Guide

A step-by-step guide to integrating Gemma 4 with Antigravity via Ollama. Covers model size selection, GPU tuning, Python API usage, config setup, and troubleshooting slow responses and memory crashes.

Gemma 422Ollama15local LLM14Antigravity325privacy5GPUPython14

When Gemma 4 was released, my first thought was: can I run this locally and connect it to Antigravity for fully offline AI-assisted development? After a few days of trial and error, the answer is yes — and the result is genuinely impressive. No API costs, no data leaving your machine, and response quality that rivals cloud models for many everyday coding tasks.

This guide walks through everything I learned, from picking the right model size to the Ollama tuning parameters that actually make a difference in practice.

Choosing the Right Gemma 4 Variant

Gemma 4 comes in three sizes, and picking the wrong one is the fastest way to get frustrated:

ModelVRAM RequiredRAM (CPU fallback)Best for
gemma4:4b4–6 GB8 GBLaptops, quick prototyping
gemma4:9b8–10 GB16 GBRecommended starting point
gemma4:27b20–24 GB32 GBWorkstations, complex reasoning

My recommendation: start with gemma4:9b. It fits comfortably on most modern developer laptops with 16 GB RAM, and quality is substantially better than the 4B for code generation and multi-step reasoning — which is exactly what you need when Antigravity is running agentic tasks.

If you're on a machine with only 8 GB RAM, gemma4:4b works but expect noticeably slower code reasoning. For pure autocomplete it's fine; for multi-file refactoring tasks, 9B is worth the extra memory.

Install and pull:

# Install Ollama (macOS)
curl -fsSL https://ollama.com/install.sh | sh
 
# Pull Gemma 4 9B (recommended)
ollama pull gemma4:9b
 
# Or 4B for lightweight setups
ollama pull gemma4:4b
 
# Verify it's working
ollama run gemma4:9b "Write a hello world in TypeScript"

Tuning Ollama for Development Workloads

Default Ollama settings are conservative — reasonable for a general server, but too slow for interactive development with Antigravity. These environment variables make a real difference:

# ~/.zshrc or ~/.bashrc — add and reload your shell
 
# GPU optimization
export OLLAMA_GPU_LAYERS=35       # 35 for 9B, 20 for 4B on 8GB VRAM
export OLLAMA_NUM_PARALLEL=2      # Concurrent request handling
export OLLAMA_FLASH_ATTENTION=1   # ~20% faster on Apple Silicon / CUDA
 
# Context and generation
export OLLAMA_CONTEXT_LENGTH=8192  # Enough for most coding tasks
export OLLAMA_MAX_LOADED_MODELS=1  # Prevent model swapping
 
# Restart Ollama to apply
pkill ollama && ollama serve &

A few notes on these settings:

OLLAMA_GPU_LAYERS is the single biggest performance lever. On an M2 MacBook Pro with 16 GB unified memory, setting this to 35 for the 9B model moves about 80% of computation to the Neural Engine, cutting response latency roughly in half compared to CPU-only.

OLLAMA_FLASH_ATTENTION=1 enables Flash Attention 2 where hardware supports it. On Apple Silicon and recent NVIDIA GPUs (Ampere+), the improvement is consistent. On older hardware, skip it — it can actually cause crashes.

OLLAMA_CONTEXT_LENGTH=8192 is a sweet spot. Going higher (32K) is tempting but increases memory pressure significantly. For Antigravity's agentic tasks, 8K handles most file-level changes comfortably.

Direct Python API Usage

Ollama exposes a REST API that's easy to use directly without installing any extra packages:

import requests
import json
 
OLLAMA_BASE = "http://localhost:11434"
 
def generate_with_ollama(prompt: str, model: str = "gemma4:9b") -> str:
    """Single-shot generation — waits for the complete response."""
    response = requests.post(
        f"{OLLAMA_BASE}/api/generate",
        json={
            "model": model,
            "prompt": prompt,
            "stream": False,
            "options": {
                "temperature": 0.2,   # Lower = more deterministic code output
                "top_p": 0.9,
                "num_predict": 2048,
            }
        },
        timeout=120
    )
    response.raise_for_status()
    return response.json()["response"]
 
 
def stream_with_ollama(prompt: str, model: str = "gemma4:9b"):
    """Streaming generation — yields tokens as they arrive."""
    response = requests.post(
        f"{OLLAMA_BASE}/api/generate",
        json={
            "model": model,
            "prompt": prompt,
            "stream": True,
            "options": {"temperature": 0.2}
        },
        stream=True,
        timeout=120
    )
    response.raise_for_status()
    for line in response.iter_lines():
        if line:
            data = json.loads(line)
            yield data.get("response", "")
            if data.get("done"):
                break
 
 
# Usage
result = generate_with_ollama("Explain what this TypeScript function does:\n\nconst debounce = (fn, delay) => ...")
print(result)
 
# Streaming
print("Generating: ", end="", flush=True)
for token in stream_with_ollama("Write a React hook for debounce"):
    print(token, end="", flush=True)
print()

The temperature: 0.2 setting is worth highlighting. For code generation, lower temperatures produce more predictable, correct output. The default of 0.7–0.8 is great for creative writing but leads to hallucinated function signatures and variable names in code. For Antigravity-style tasks — where you're asking the model to edit existing files — I'd keep it at 0.1–0.3.

Using the OpenAI-Compatible Endpoint

If you have existing code that uses the OpenAI Python SDK, you can point it at Ollama without changing your client code:

from openai import OpenAI
 
# Drop-in replacement: just change base_url and model name
client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",  # Ollama ignores this value but the SDK requires it
)
 
def code_review(code: str) -> str:
    response = client.chat.completions.create(
        model="gemma4:9b",
        messages=[
            {
                "role": "system",
                "content": "You are a senior software engineer reviewing code for bugs, performance issues, and best practices."
            },
            {
                "role": "user",
                "content": f"Review this code:\n\n```\n{code}\n```"
            }
        ],
        temperature=0.2,
        max_tokens=2048,
    )
    return response.choices[0].message.content
 
 
# Works exactly like the OpenAI API
review = code_review("""
async function fetchUser(id) {
  const res = await fetch(`/api/users/${id}`)
  return res.json()
}
""")
print(review)

This compatibility layer is genuinely useful if you're migrating a project from OpenAI to a local LLM. The chat completion format maps directly, so multi-turn conversation history works as expected.

Connecting Antigravity to Your Local Ollama Server

Create or update .antigravity/config.json in your project root:

{
  "provider": "ollama",
  "model": "gemma4:9b",
  "baseUrl": "http://localhost:11434",
  "contextWindow": 8192,
  "temperature": 0.2,
  "agentMode": {
    "enabled": true,
    "maxIterations": 10,
    "autoConfirm": false
  },
  "privacy": {
    "localOnly": true,
    "telemetry": false
  }
}

A few things worth noting here:

autoConfirm: false is important for agentic tasks. When Gemma 4 is running multi-step file edits, you want a human checkpoint before each write operation — especially while you're learning how the model behaves on your codebase. Once you're confident in the model's accuracy for a specific task type, you can toggle this on selectively.

maxIterations: 10 prevents runaway agent loops. If Antigravity hasn't solved the task in 10 steps, something is probably wrong with the prompt rather than requiring more iterations.

For remote team setups — e.g., running Ollama on a beefy workstation and connecting from a laptop — change baseUrl to the workstation's LAN IP and make sure port 11434 is accessible within your network.

Four Troubleshooting Scenarios

1. Responses Are Too Slow

The most common complaint. First, verify GPU acceleration is actually active:

# Check if GPU layers are being used
ollama ps
 
# You should see something like:
# NAME          ID      SIZE    PROCESSOR    UNTIL
# gemma4:9b     abc123  7.4 GB  100% GPU     forever

If PROCESSOR shows 100% CPU, your GPU layers setting isn't taking effect. On Apple Silicon, make sure you're using the ARM native Ollama binary (not Rosetta). On NVIDIA, verify CUDA is installed with nvidia-smi.

If GPU acceleration is working but responses are still slow, the bottleneck is usually context length. Try reducing OLLAMA_CONTEXT_LENGTH from 8192 to 4096 — this alone can cut latency by 30–40% for shorter prompts.

2. Connection Refused from Antigravity

Error: connect ECONNREFUSED 127.0.0.1:11434

Antigravity can't reach the Ollama server. Work through this checklist:

# 1. Is Ollama running?
pgrep -l ollama
 
# 2. Start it if not
ollama serve
 
# 3. Test the endpoint directly
curl http://localhost:11434/api/tags
# Should return a JSON list of your installed models
 
# 4. If you're using a non-default port, match it in config.json
OLLAMA_HOST=0.0.0.0:11435 ollama serve
# Then set "baseUrl": "http://localhost:11435" in config.json

One gotcha on macOS: if you installed Ollama as a background service via the menubar app, it may start on a different port after a reboot. Check with lsof -i :11434 to confirm what's actually listening.

3. Generation Stops Mid-Response

Antigravity shows a partial response and then nothing. Two common causes:

Timeout: The timeout_ms for the underlying API call is too short. Gemma 4 9B generating a long response can take 30–60 seconds on CPU. In your Antigravity settings, increase the request timeout to at least 120 seconds.

Context overflow: The prompt plus existing context exceeded OLLAMA_CONTEXT_LENGTH. Symptoms include truncated responses that end mid-sentence. Fix:

# Increase context length (requires more VRAM/RAM)
export OLLAMA_CONTEXT_LENGTH=16384
pkill ollama && ollama serve &

If memory is tight and you can't increase context length, add a .antigravityignore file to exclude large generated files and build artifacts from the context:

# .antigravityignore
node_modules/
.next/
dist/
*.lock
*.map
coverage/

4. Out of Memory Crash

The model loads, generates a few responses, and then Ollama crashes with an OOM error. The fix is almost always switching to a quantized version of the model:

# Instead of the default (float16)
ollama pull gemma4:9b
 
# Try quantized versions (much lower memory usage)
ollama pull gemma4:9b-q4_0    # ~5 GB VRAM — aggressive compression
ollama pull gemma4:9b-q5_k_m  # ~6 GB VRAM — better quality/size balance
ollama pull gemma4:9b-q8_0    # ~9 GB VRAM — near-lossless quality

For most coding tasks, q5_k_m is the sweet spot — the quality difference from the full model is minimal, and memory usage drops by about 40%. I've been using this on an M2 Pro (16 GB) for months without quality issues.

After switching to a quantized model, update your config.json:

{
  "model": "gemma4:9b-q5_k_m"
}

Putting It All Together: A Practical Usage Pattern

Here's how I actually use Gemma 4 + Antigravity in day-to-day development:

import subprocess
import requests
import json
 
def check_ollama_health() -> bool:
    """Quick health check before starting a work session."""
    try:
        response = requests.get("http://localhost:11434/api/tags", timeout=5)
        models = [m["name"] for m in response.json().get("models", [])]
        if not any("gemma4" in m for m in models):
            print("Gemma 4 not installed. Run: ollama pull gemma4:9b-q5_k_m")
            return False
        print(f"Ollama ready. Models: {', '.join(models)}")
        return True
    except requests.ConnectionError:
        print("Ollama not running. Starting...")
        subprocess.Popen(["ollama", "serve"],
                        stdout=subprocess.DEVNULL,
                        stderr=subprocess.DEVNULL)
        return False
 
 
def ask_about_code(file_path: str, question: str) -> str:
    """Ask Gemma 4 a question about a specific file."""
    with open(file_path) as f:
        code = f.read()
 
    prompt = f"""File: {file_path}
 

{code}


Question: {question}

Please be specific and reference line numbers or function names where relevant."""

    return generate_with_ollama(prompt, model="gemma4:9b-q5_k_m")


# Start-of-session check
if check_ollama_health():
    # Ask about a specific file
    answer = ask_about_code(
        "src/components/AuthProvider.tsx",
        "Are there any race conditions in the token refresh logic?"
    )
    print(answer)

The health check pattern is worth adopting — it gives you a clear error message instead of a cryptic connection failure when you forget to start Ollama before opening Antigravity.

What to Expect from the Local Setup

Compared to cloud-hosted models, Gemma 4 9B running locally is:

Better at: Privacy-sensitive tasks, repeated similar queries (no rate limits), offline development, low-latency responses for short prompts once the model is warm.

Worse at: Very long context tasks (>16K tokens), complex multi-step reasoning chains, tasks requiring knowledge of events after the model's training cutoff.

For day-to-day coding — writing tests, explaining unfamiliar code, suggesting refactors, generating boilerplate — the local setup handles 80–90% of my Antigravity usage without needing to reach out to a cloud API. The remaining 10–20% (complex architecture discussions, long-context analysis) I route to Claude or Gemini via Antigravity's multi-provider support.

That balance — local for routine work, cloud for edge cases — is where I've landed after months of use. Your numbers will vary by hardware, but the workflow itself scales well.

Multi-Turn Conversation Management

Agentic workflows in Antigravity often involve multi-turn conversations where context from earlier exchanges needs to be preserved. Here's a session management pattern that keeps memory usage predictable:

from dataclasses import dataclass, field
from typing import List, Optional
import requests
import json
 
@dataclass
class Message:
    role: str  # "user" or "assistant"
    content: str
 
@dataclass
class OllamaSession:
    model: str = "gemma4:9b-q5_k_m"
    system_prompt: str = "You are an expert software engineer helping with code review and development."
    history: List[Message] = field(default_factory=list)
    max_history_tokens: int = 4096  # Keep history within bounds
 
    def _estimate_tokens(self, text: str) -> int:
        """Rough estimate: 1 token ≈ 4 characters."""
        return len(text) // 4
 
    def _trim_history(self):
        """Remove oldest messages when history gets too long."""
        while self.history:
            total = sum(self._estimate_tokens(m.content) for m in self.history)
            if total < self.max_history_tokens:
                break
            self.history.pop(0)  # Remove oldest message
 
    def chat(self, user_message: str) -> str:
        self.history.append(Message(role="user", content=user_message))
        self._trim_history()
 
        # Build messages list for Ollama
        messages = [{"role": "system", "content": self.system_prompt}]
        messages += [{"role": m.role, "content": m.content} for m in self.history]
 
        response = requests.post(
            "http://localhost:11434/api/chat",
            json={
                "model": self.model,
                "messages": messages,
                "stream": False,
                "options": {"temperature": 0.2}
            },
            timeout=120
        )
        response.raise_for_status()
 
        assistant_reply = response.json()["message"]["content"]
        self.history.append(Message(role="assistant", content=assistant_reply))
        return assistant_reply
 
    def reset(self):
        self.history = []
 
 
# Usage: multi-turn code review session
session = OllamaSession(
    system_prompt="You are reviewing a TypeScript codebase. Be concise and specific."
)
 
# Turn 1: Initial question
print(session.chat("What patterns should I use for error handling in async TypeScript?"))
 
# Turn 2: Follow-up with code
code = """
async function fetchProfile(userId: string) {
  const res = await fetch(`/api/profiles/${userId}`)
  const data = await res.json()
  return data
}
"""
print(session.chat(f"Here's my current implementation:\n```typescript\n{code}\n```\nWhat's missing?"))
 
# Turn 3: Ask for a fix
print(session.chat("Can you rewrite this with proper error handling?"))

The _trim_history method is important for long sessions — without it, the accumulated context grows until Ollama hits its context window limit and starts producing degraded responses. Trimming from the oldest end preserves the most relevant recent context.

Benchmarking Your Setup

Before committing to a model variant and tuning configuration for your team, it's worth running a quick benchmark to establish baseline performance:

import time
import statistics
from typing import Callable
 
def benchmark_model(
    prompt: str,
    generate_fn: Callable,
    runs: int = 5,
    warmup: int = 1
) -> dict:
    """Measure generation latency across multiple runs."""
    latencies = []
 
    # Warmup — first run is often slower due to model loading
    for _ in range(warmup):
        generate_fn(prompt)
 
    for i in range(runs):
        start = time.perf_counter()
        response = generate_fn(prompt)
        elapsed = time.perf_counter() - start
        latencies.append(elapsed)
        tokens = len(response.split())  # Rough token count
        print(f"Run {i+1}: {elapsed:.1f}s, ~{tokens} tokens, ~{tokens/elapsed:.0f} tok/s")
 
    return {
        "mean_latency": statistics.mean(latencies),
        "p50_latency": statistics.median(latencies),
        "p95_latency": sorted(latencies)[int(0.95 * len(latencies))],
        "min_latency": min(latencies),
        "max_latency": max(latencies),
    }
 
# Test prompts representative of real Antigravity workloads
SHORT_PROMPT = "Explain what a React useEffect cleanup function does in 2 sentences."
MEDIUM_PROMPT = """Review this function and suggest improvements:
 
```typescript
export async function getUser(id: string) {
  try {
    const user = await db.users.findOne({ id })
    if (\!user) return null
    return user
  } catch (e) {
    console.error(e)
    return null
  }
}
```"""
LONG_PROMPT = """I have a Next.js application with the following structure:
- app/api/auth/[...nextauth]/route.ts — NextAuth config
- app/dashboard/page.tsx — Protected dashboard
- components/AuthProvider.tsx — Session provider
- lib/db.ts — Prisma client
 
The issue: users are getting logged out randomly after about 20 minutes. Analyze possible causes in order of likelihood and suggest debugging steps."""
 
generate = lambda p: generate_with_ollama(p, model="gemma4:9b-q5_k_m")
 
print("=== Short prompt benchmark ===")
short_stats = benchmark_model(SHORT_PROMPT, generate)
print(f"Mean: {short_stats['mean_latency']:.1f}s\n")
 
print("=== Medium prompt benchmark ===")
medium_stats = benchmark_model(MEDIUM_PROMPT, generate)
print(f"Mean: {medium_stats['mean_latency']:.1f}s\n")

On my M2 Pro (16 GB), typical results with gemma4:9b-q5_k_m after tuning:

  • Short prompts: 3–5 seconds
  • Medium prompts (code review): 8–15 seconds
  • Long prompts (multi-file analysis): 25–45 seconds

If your numbers are 3× higher than these, the tuning parameters from the earlier section haven't taken effect — double-check that ollama ps shows GPU acceleration active.

Integrating with Your CI Pipeline (Optional)

For teams using Antigravity, you can add automated code review to pull request workflows without any cloud API dependency:

# scripts/local_review.py
"""
Run before git push to catch obvious issues.
Usage: python scripts/local_review.py --files src/auth.ts src/db.ts
"""
import argparse
import sys
from pathlib import Path
 
def review_files(file_paths: list[str]) -> bool:
    """Returns True if review passes, False if issues found."""
    issues_found = False
 
    for path in file_paths:
        content = Path(path).read_text()
 
        # Skip generated files, tests, etc.
        if any(skip in path for skip in ["generated", "__tests__", ".d.ts"]):
            continue
 
        print(f"\n📝 Reviewing {path}...")
        review = generate_with_ollama(
            f"""Review this code for: security issues, potential runtime errors, 
and obvious performance problems. Be brief — flag issues only, skip praise.
If no issues found, respond with exactly: "LGTM"
 
File: {path}

{content}

            model="gemma4:9b-q5_k_m"
        )
 
        if review.strip() \!= "LGTM":
            print(f"⚠️  Issues found:\n{review}")
            issues_found = True
        else:
            print("✅ LGTM")
 
    return not issues_found
 
 
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--files", nargs="+", required=True)
    args = parser.parse_args()
 
    if not check_ollama_health():
        print("Skipping review — Ollama not available")
        sys.exit(0)  # Don't block commits if Ollama is down
 
    passed = review_files(args.files)
    sys.exit(0 if passed else 1)

Add this to a pre-push hook:

# .git/hooks/pre-push
#\!/bin/bash
CHANGED=$(git diff --name-only HEAD~1 HEAD -- "*.ts" "*.tsx" "*.py")
if [ -n "$CHANGED" ]; then
    python scripts/local_review.py --files $CHANGED
fi

The key design decision here is sys.exit(0) when Ollama isn't available — the review is a helper, not a gate. You don't want a CI step that fails because a developer forgot to start their local LLM server.

Comparing Gemma 4 Variants for Coding Tasks

After running each variant through representative coding tasks, here's what the quality differences actually look like in practice:

Bug detection accuracy (finding intentional bugs in 20 code samples):

  • gemma4:4b: ~65% detection rate, frequent false positives
  • gemma4:9b-q5_k_m: ~82% detection rate, accurate explanations
  • gemma4:27b: ~89% detection rate, occasionally over-explains

Code generation correctness (TypeScript functions with tests):

  • gemma4:4b: Works for simple functions, struggles with generics and complex async patterns
  • gemma4:9b-q5_k_m: Handles most patterns correctly, occasionally needs one round of correction
  • gemma4:27b: Near-GPT-4 quality for well-defined tasks, but slow on consumer hardware

Multi-file refactoring (the core Antigravity use case):

  • gemma4:4b: Often loses track of context across files
  • gemma4:9b-q5_k_m: Solid for 3–5 file refactors; struggles beyond that
  • gemma4:27b: Handles larger refactors well, but 45+ second latency per step

My current recommendation remains gemma4:9b-q5_k_m for most developers. The jump to 27B is only worth it if you're regularly asking Antigravity to reason across 10+ files simultaneously and your hardware can handle the memory requirement.

Next Steps

With this setup running, the natural next place to invest time is .antigravityignore tuning — teaching Antigravity which parts of your codebase are worth including in the model's context and which aren't. Getting this right dramatically improves the relevance of suggestions and reduces the latency of multi-file operations. Start with excluding node_modules, lock files, and generated code, then refine based on which file types come up repeatedly in Antigravity's context windows.

If you hit issues not covered here, the Ollama GitHub issues page and the Antigravity Discord are both active — the local LLM community has generally good collective knowledge about hardware-specific quirks.

Prompt Engineering for Code Tasks

How you structure prompts for Gemma 4 matters more than it does with larger cloud models. Smaller models are more sensitive to prompt format — a well-structured prompt can be the difference between a useful response and a confused one.

Patterns that work well with Gemma 4:

Explicit format requests:

# Less effective
prompt = "Review this code: " + code
 
# More effective
prompt = f"""Review the following TypeScript code.
 
Format your response as:
1. Issues (if any): bullet points with line numbers
2. Suggested fix: code block
3. Explanation: 2-3 sentences max
 
Code:
```typescript
{code}
```"""

Role + constraint framing:

prompt = """You are a senior TypeScript engineer doing a focused security review.
Your job: identify only input validation and injection vulnerabilities.
Do not comment on style, performance, or unrelated issues.
 
Code to review:
```typescript
{code}

List vulnerabilities as: [SEVERITY] Line N: description"""


Step-by-step decomposition for complex tasks:

```python
# Instead of asking for everything at once
prompt = f"""Task: Refactor this function to use async/await instead of callbacks.

Step 1: Identify all callback patterns in the code below.
Step 2: Convert each one to async/await.
Step 3: Handle errors with try/catch.
Step 4: Preserve the exact same public interface.

Original code:
```javascript
{code}
```"""

The step-by-step format is particularly effective for Gemma 4 9B on refactoring tasks. It guides the model through the problem incrementally, reducing the chance that it skips a callback or introduces an inconsistency.

Common prompt mistakes that hurt Gemma 4 quality:

Asking multiple unrelated questions in one prompt leads to incomplete answers. Split them into separate calls.

Providing too much context without a clear question causes the model to summarize rather than analyze. Be specific about what you want.

Asking for "improvements" without constraints produces generic suggestions. Specify the constraints: "Improve this without changing the function signature and without introducing new dependencies."

Monitoring Token Usage and Costs

Even though Gemma 4 is free to run locally, it's worth tracking usage — primarily to understand how much context your Antigravity workflows are actually consuming and to identify prompts that are inefficiently long:

import json
import datetime
from pathlib import Path
 
LOG_FILE = Path(".antigravity/usage_log.jsonl")
 
def generate_with_logging(prompt: str, model: str = "gemma4:9b-q5_k_m") -> str:
    """Generates a response and logs token usage."""
    import time
    start = time.perf_counter()
 
    response = requests.post(
        "http://localhost:11434/api/generate",
        json={
            "model": model,
            "prompt": prompt,
            "stream": False,
            "options": {"temperature": 0.2}
        },
        timeout=120
    )
    response.raise_for_status()
    elapsed = time.perf_counter() - start
    data = response.json()
 
    # Log the usage
    log_entry = {
        "timestamp": datetime.datetime.now().isoformat(),
        "model": model,
        "prompt_tokens": len(prompt.split()),  # Rough estimate
        "response_tokens": len(data["response"].split()),
        "latency_seconds": round(elapsed, 2),
        "eval_count": data.get("eval_count", 0),  # Actual token count from Ollama
        "prompt_eval_count": data.get("prompt_eval_count", 0),
    }
 
    LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
    with open(LOG_FILE, "a") as f:
        f.write(json.dumps(log_entry) + "\n")
 
    return data["response"]
 
 
def analyze_usage(days: int = 7) -> None:
    """Print a usage summary for the last N days."""
    if not LOG_FILE.exists():
        print("No usage log found.")
        return
 
    cutoff = datetime.datetime.now() - datetime.timedelta(days=days)
    entries = []
 
    with open(LOG_FILE) as f:
        for line in f:
            entry = json.loads(line)
            if datetime.datetime.fromisoformat(entry["timestamp"]) > cutoff:
                entries.append(entry)
 
    if not entries:
        print(f"No usage in the last {days} days.")
        return
 
    total_prompt_tokens = sum(e.get("prompt_eval_count", 0) for e in entries)
    total_response_tokens = sum(e.get("eval_count", 0) for e in entries)
    avg_latency = sum(e["latency_seconds"] for e in entries) / len(entries)
 
    print(f"=== Usage Summary (last {days} days) ===")
    print(f"Total requests: {len(entries)}")
    print(f"Total prompt tokens: {total_prompt_tokens:,}")
    print(f"Total response tokens: {total_response_tokens:,}")
    print(f"Average latency: {avg_latency:.1f}s")
    print(f"Slowest request: {max(e['latency_seconds'] for e in entries):.1f}s")
 
    # Find expensive prompts
    expensive = sorted(entries, key=lambda e: e.get("prompt_eval_count", 0), reverse=True)[:3]
    print("\nTop 3 largest prompts (by token count):")
    for e in expensive:
        print(f"  {e['timestamp']}: {e.get('prompt_eval_count', 0):,} tokens, {e['latency_seconds']:.1f}s")
 
 
# Analyze your usage
analyze_usage(days=7)

This kind of monitoring is most useful when you're first setting up Antigravity for a team — it quickly reveals which prompt templates are using disproportionate context, and where trimming history or tightening .antigravityignore would have the biggest impact on response speed.

Privacy and Data Governance Considerations

One of the primary reasons to choose a local LLM setup is data privacy. But "local" doesn't automatically mean "fully private" — there are a few things worth explicitly checking:

Ollama telemetry: By default, Ollama does not send telemetry or usage data. Verify by checking your firewall logs or running OLLAMA_NOPRUNE=1 ollama serve and monitoring outbound connections with lsof -i -n | grep ollama.

Antigravity telemetry: If your organization has data residency requirements, confirm that Antigravity's telemetry settings are configured appropriately. The "telemetry": false flag in config.json disables Antigravity's own usage reporting, but verify this in the official documentation for your version.

Model files: Gemma 4 model files are stored in ~/.ollama/models/ on macOS/Linux. These are large binary files containing the model weights — not your code. There's no "call home" behavior from the model weights themselves.

Log files: If you implement the usage logging from the previous section, the log contains your prompt text. Apply appropriate access controls to .antigravity/usage_log.jsonl if your codebase contains sensitive business logic.

For most individual developers and small teams, the default Ollama + Antigravity setup is effectively air-gapped once running — all inference happens on your hardware, no API calls to external services. This is the core advantage over cloud-hosted solutions and worth taking the time to verify for your specific environment.

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

AI Tools2026-06-14
Pairing a Local LLM With Antigravity to Keep Sensitive Code Off the Cloud
Should you really let a cloud agent read code that holds your billing keys and revenue logic? For indie developers that worry is concrete. Here I pair Ollama and Gemma as a local LLM with Antigravity, routing sensitive parts to local and general parts to the cloud, with the decision rules and measurements.
Antigravity2026-05-02
Gemma 4 × Antigravity Complete Practical Guide — Local LLM, RAG, Ollama/LM Studio Integration
A practical, production-grade guide to running Antigravity with Gemma 4 — covering local LLM setup, RAG pipelines, Ollama/LM Studio integration, and fine-tuning. Includes troubleshooting and operational best practices.
Integrations2026-04-24
Antigravity × Ollama — The Complete Guide to Running Local LLMs (Gemma 4 Edition)
A hands-on guide to wiring Ollama into Antigravity so you can run Gemma 4 locally. Covers cross-OS setup, endpoint configuration, model sizing decisions, and a real-world fallback strategy for offline development, sensitive data, and cost control.
📚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
See all →