◉ANTIGRAVITY LABJP
Articles/Agents & Manager
◈ Agents & Manager/2026-04-26Advanced

Designing Antigravity Agent Traces That Tell You Why It Failed — Observability in Practice

Run Antigravity agents long enough and unreadable failure logs pile up fast. This piece walks span structure, attribute design, failure tagging, dashboards, cost visibility, and retry policy — backed by six months of production metrics — so you can cut post-incident debugging time in half.

Antigravity377agents144observability19tracing3operations33OpenTelemetry

✦ Premium Article

It took six months of running Antigravity agents in production for the first piece of operational debt to show: failure traces I could not read. Early on I sprinkled console.log calls across the agent loop, and when execution volume was a few dozen runs per day I could scroll. Once it crossed a few thousand runs per month, errors were happening but I could no longer tell which step of which cycle had broken.

After six months of investing in observability I am at the point where three lines of a trace usually point at the cause. Mean time to resolution (MTTR) for incidents dropped from 47 minutes pre-instrumentation to 11 minutes today. This article shares the trace design I run against my own Antigravity agents — span structure, attributes, failure tagging, dashboards, and retry policy — at a level of detail you can copy into your own repo.

Having shipped mobile apps since 2014, I remember it taking three months on my first wallpaper app to make production crash reports actually readable. Instrumenting agents was the same job in a new costume.

Why agents fall into the "I cannot tell why it failed" hole

The patterns I have observed cluster into three causes.

First, fuzzy step boundaries. Agents cycle through "tool call → LLM inference → state update," and without logging that boundary you cannot tell which step of which cycle blew up. Time-stamped flat logs hide the cycle structure.

Second, opaque inputs and outputs. Teams log "what happened inside the LLM call" but not the context passed in or the raw response returned. Failures collapse into "the LLM said something weird" and become unreproducible.

Third, no failure taxonomy. Errors are logged, but you cannot tell after the fact whether the failure was retryable, deserved a user notification, or was a plain code bug. That triggers alert fatigue and broken retry policies.

Observability design is about wiring structure for these three up front.

The base unit of a trace — pick a span structure

I align with OpenTelemetry semantics and represent agent execution like this. The shape is generic, not Antigravity-specific, but inserting a "cycle" level matters.

- agent.run               (root span — one full agent execution)
  - agent.cycle           (a single reasoning + execution cycle)
    - agent.plan          (LLM picks next action)
    - agent.tool_call     (tool execution — separate span per call)
    - agent.observe       (folding the tool result back into context)
  - agent.cycle
    - ...

Making agent.cycle its own span is the load-bearing decision. When an agent runs eight cycles and fails on the fifth, without cycle-level spans a human has to walk the log to recover the fact that "cycle five hit a tool error."

Implementing this is just wrapping the cycle in tracer.start_as_current_span("agent.cycle"). In Python:

from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode
 
tracer = trace.get_tracer("antigravity.agent")
 
def run_agent(task):
    with tracer.start_as_current_span("agent.run") as run_span:
        run_span.set_attribute("task.id", task.id)
        run_span.set_attribute("task.kind", task.kind)
        run_span.set_attribute("agent.policy", task.policy_name)
        run_span.set_attribute("agent.tools_available", list(task.tools))
 
        for cycle_idx in range(MAX_CYCLES):
            with tracer.start_as_current_span("agent.cycle") as cycle_span:
                cycle_span.set_attribute("cycle.index", cycle_idx)
 
                try:
                    action = plan_next_action(task)
                    if action is None:
                        cycle_span.set_attribute("cycle.terminated", True)
                        run_span.set_attribute("result.status", "success")
                        return success(task)
 
                    result = execute_tool(action)
                    observe(task, result)
                    cycle_span.set_attribute("cycle.action_type", action.kind)
                except AgentError as e:
                    cycle_span.set_status(Status(StatusCode.ERROR, str(e)))
                    cycle_span.set_attribute("result.failure_class", e.failure_class)
                    run_span.set_attribute("result.status", "failure")
                    run_span.set_attribute("result.failure_class", e.failure_class)
                    raise

In TypeScript (Node.js) with @opentelemetry/api the same structure looks like:

import { trace, SpanStatusCode } from "@opentelemetry/api";
 
const tracer = trace.getTracer("antigravity.agent");
 
export async function runAgent(task: Task): Promise<Result> {
  return await tracer.startActiveSpan("agent.run", async (runSpan) => {
    runSpan.setAttribute("task.id", task.id);
    runSpan.setAttribute("task.kind", task.kind);
    runSpan.setAttribute("agent.policy", task.policyName);
 
    try {
      for (let cycleIdx = 0; cycleIdx < MAX_CYCLES; cycleIdx++) {
        const done = await tracer.startActiveSpan(
          "agent.cycle",
          async (cycleSpan) => {
            cycleSpan.setAttribute("cycle.index", cycleIdx);
            const action = await planNextAction(task);
            if (action === null) {
              cycleSpan.setAttribute("cycle.terminated", true);
              return true;
            }
            cycleSpan.setAttribute("cycle.action_type", action.kind);
            const result = await executeTool(action);
            observe(task, result);
            cycleSpan.end();
            return false;
          }
        );
        if (done) {
          runSpan.setAttribute("result.status", "success");
          return success(task);
        }
      }
    } catch (e) {
      runSpan.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
      runSpan.setAttribute("result.failure_class", classify(e));
      throw e;
    } finally {
      runSpan.end();
    }
  });
}

One catch: always set cycle.index on agent.cycle. You will end up writing the query "show me what happened in the third cycle."

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦OpenTelemetry-aligned four-level span structure (agent.run / agent.cycle / agent.plan / agent.tool_call) with copy-pasteable Python and TypeScript implementations
✦An eight-class failure_class taxonomy paired with a retry-policy and user-notification matrix you can drop into your repo today
✦Real production numbers from six months of operation — average cycle count 4.2 → 2.8, token consumption down 35%, MTTR 47 min → 11 min — plus three dashboard query examples
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
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 →

Related Articles

◈ Agents & Manager2026-09-06
With scheduled agents, I now look for runs that never happened before I look for failures
My run ledger showed a 100% success rate while the evening slot had not fired for two weeks. Here is the reconciliation I now run against an expected-fire table, with measured notes on cron expansion, exit codes, and how the ledger itself gets written.
◈ Agents & Manager2026-09-08
Before I accept what /boost says it verified, I count what its throwaway worktree never saw
Antigravity's /boost runs its tests inside an ephemeral isolated worktree. If your build reaches for files git never tracked, that verification passed on a different tree than yours. Here is how I measure the gap first.
◈ Agents & Manager2026-08-16
Android 17 Went Canary-Only, So I Gave My Agent the Silence Rules First
Android 17 dropped Developer Previews in favour of rolling Canary builds, which moved the verification deadline onto my side of the table. Here is how I handed the tracking job to an Antigravity agent by designing when it stays quiet, not when it reports.
📚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