Antigravity CLI session tracing with Future AGI
Trace Antigravity CLI agent conversations on macOS in Future AGI with Antigravity lifecycle hooks: one trace per conversation, with turns, model calls, tool calls, model name, and errors. Prompts and tool arguments stay off by default.
Trace Antigravity CLI conversations in Future AGI using lifecycle hooks and the Future AGI hook-to-OTLP emitter. Each supported CLI conversation appears as one trace:
Antigravity session one conversation (conversationId), model name, cli
├── Antigravity turn one user message, from the first model call to Stop
│ ├── Model call PreInvocation to PostInvocation, model name
│ ├── run_command tool call (PostToolUse): name, status
│ ├── view_file
│ └── Model call
└── Antigravity turn the next message in the same conversation
├── Model call
├── write_to_file ERROR when the tool returns an error
└── Model call
Antigravity has no OpenTelemetry export; hooks are its documented extension point, so this setup uses them.
This is not the same as:
- Gemini CLI. Gemini CLI has its own OpenTelemetry settings and a separate integration. Its settings do not affect Antigravity, and its sessions are not labelled Antigravity.
- The Antigravity app (Antigravity 2.0). It shares the hook schema and reads the same workspace and global hooks files, but is not covered here. The emitter still runs there and adds synchronous hook latency, while this adapter drops its events by app-data path.
- Google ADK and the Gemini API. For tracing your own code that calls Gemini, use a traceAI integration.
Before you start
Use Antigravity CLI 1.4.2 on macOS, Apple silicon, workspace hooks and a self-hosted Future AGI collector. Python 3.9+ must be on the PATH Antigravity uses; no Python packages are required. IDE, desktop-app, global-hook and native Windows setups are outside this recipe.
The Antigravity example and hook-to-OTLP source contain the installer and adapter.
1. Install the hook
Clone the examples and run the installer against your project. It copies the emitter and the Antigravity adapter into .agents/fi-hook-to-otlp/ and writes .agents/hooks.json. If the project already has a hooks.json, the installer leaves it alone and prints the "fi-hook-to-otlp" member to add to its top-level object.
git clone --depth 1 https://github.com/future-agi/docs.git fi-docs
fi-docs/examples/coding-agents/google-antigravity/install.sh /path/to/your/project
The CLI reads .agents/hooks.json at the project root. It looks for .agents/ from the folder you start agy in up to the repository root (the folder that contains .git), so start it inside the project.
hooks.json maps a hook name to its events. Antigravity payloads do not name the event, so each command passes it as an argument. The workspace file looks like this:
{
"fi-hook-to-otlp": {
"SessionStart": [{ "type": "command", "command": "(python3 -B fi-hook-to-otlp/emit.py antigravity SessionStart 2>/dev/null || true)", "timeout": 10 }],
"PreInvocation": [{ "type": "command", "command": "(python3 -B fi-hook-to-otlp/emit.py antigravity PreInvocation 2>/dev/null || true)", "timeout": 10 }],
"PostInvocation": [{ "type": "command", "command": "(python3 -B fi-hook-to-otlp/emit.py antigravity PostInvocation 2>/dev/null || true)", "timeout": 10 }],
"PostToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "(python3 -B fi-hook-to-otlp/emit.py antigravity PostToolUse 2>/dev/null || true)", "timeout": 10 }] }],
"Stop": [{ "type": "command", "command": "(python3 -B fi-hook-to-otlp/emit.py antigravity Stop 2>/dev/null || true)", "timeout": 10 }]
}
}
Antigravity runs hook commands with sh -c, in the folder that contains hooks.json, with the payload on stdin. The workspace file’s emitter path is therefore relative to .agents/ and can be committed. Type /hooks in the CLI to inspect or disable it.
Note
The emitter only observes. The recipe does not register PreToolUse: that is a permission hook (its answer can allow, deny, or ask), and this recipe is observe-only. It registers PostToolUse for completed tool spans. On Stop it answers {"decision": ""}, never "continue", so the agent stops as it normally would; every other hook gets {}. Every command is guarded: || true turns any failure (emitter file missing, python3 missing, crash) into exit 0 with no output, and 2>/dev/null keeps Python’s error text out of Antigravity. The parentheses run the command in a subshell, so the payload reaches python3 whether it arrives on stdin (as in Antigravity) or as a trailing heredoc. -B keeps __pycache__ out of your repository.
Native Windows is outside the runnable scope: its cmd /c hook shell does not understand the POSIX guard shown here. This guide prescribes macOS only.
2. Add your Future AGI keys
Put the keys and your reachable self-hosted collector origin in the emitter’s env file:
mkdir -p ~/.config/fi-hook-to-otlp
cat > ~/.config/fi-hook-to-otlp/env <<'EOF'
FI_API_KEY=your-futureagi-api-key
FI_SECRET_KEY=your-futureagi-secret-key
FI_PROJECT_NAME=antigravity-sessions
FI_BASE_URL=http://localhost:14338
EOF
chmod 600 ~/.config/fi-hook-to-otlp/env
The CLI’s hook commands inherit the environment of the agy process, so in a terminal you can instead export FI_API_KEY, FI_SECRET_KEY, and FI_PROJECT_NAME before you start agy. The env file is the setup shown here.
Replace FI_BASE_URL above with your self-hosted Future AGI origin (traces go to $FI_BASE_URL/tracer/v1/traces). All settings are on the hook-to-OTLP page.
3. Run an agent conversation
In the project folder, run agy and send a message that uses a tool, or run once without the interactive UI:
cd /path/to/your/project
agy -p "List the files in this folder and read README.md"
Open Observe in Future AGI and select the project (antigravity-sessions above). The conversation appears as one trace named Antigravity session, with one Antigravity turn per message. Each agy -p run starts a new conversation, so a new trace; agy -p "..." --conversation <id> (or --continue) adds a turn to the existing trace.
What is captured
| Antigravity hook | Future AGI span | Fields |
|---|---|---|
SessionStart | Antigravity session (root) | session.id = conversationId, model name, antigravity.surface=cli (from the payload’s app-data directory). Not sent again when a conversation is resumed. |
PreInvocation with invocationNum 0 | Antigravity turn start | model name, antigravity.surface. A resumed conversation starts again at 0, so its next message opens a new turn. |
PreInvocation / PostInvocation | Model call (LLM) | model name (modelName), invocation number. In CLI 1.4.2, PostInvocation fires after the tools that model response requested have run, so the span’s duration includes those tools. |
PostToolUse | completed tool span named after the tool (view_file, run_command, write_to_file, replace_file_content, …) | gen_ai.tool.name, gen_ai.tool.call.id = step-<stepIdx>, ERROR when error is set (for example an edit whose target text is not in the file, or a write that is denied); no duration |
Stop | Antigravity turn end | termination reason, execution number, idle flag. CLI 1.4.2 sends executor names such as NO_TOOL_CALL, ERROR, or USER_CANCELED; the docs show model_stop, max_steps_exceeded, and error. error or ERROR marks the turn ERROR. |
Antigravity has no session-end hook, so the session span stays open and is updated each time new spans arrive.
What is not available
- Token counts and cost. Hook payloads do not include them.
agy --output-format jsonprints a usage summary when a run ends, but no hook receives it. - Prompt, model output, and tool results in the CLI. CLI 1.4.2 payloads carry none of them, so with content capture on, a CLI trace only gains tool arguments and tool error text. This CLI recipe makes no prompt, model-output or tool-result capture promise.
- Tool call ids. Antigravity gives a step index, not a call id; the span’s
gen_ai.tool.call.idisstep-<stepIdx>. - Tool duration. Tool spans come from
PostToolUsealone, becausePreToolUseis a permission hook and is not registered, so they have no duration. - Tool calls rejected before they run. In CLI 1.4.2, a tool call that fails Antigravity’s own argument checks (for example
view_fileon a file it cannot read) fires noPostToolUseand has no span. The model call that asked for it is still traced. - Session end. There is no hook for closing a conversation.
- Subagent work. Subagent work is not linked to the parent conversation; the
invoke_subagentcall is one tool span. Nested subagent sessions are outside the supported scope. - The Antigravity app. It writes conversations under
~/.gemini/antigravity. The adapter recognizes that location in the payload (with/or\separators) and drops the event. IDE (~/.gemini/antigravity-ide) and CLI (~/.gemini/antigravity-cli) conversations are exported. This is a best-effort check on the payload paths: an event that carries neither path is exported withoutantigravity.surface. - Transcript and artifacts. The payload names a transcript file and an artifact directory. The emitter never opens or exports them. With content capture on, tool arguments can still contain workspace or artifact paths, such as
Cwd,AbsolutePath, orTargetFile.
Privacy
By default only structure and metadata leave your machine: conversation id, model name, tool names, step and invocation numbers, statuses, termination reason, cli, and timing.
- Not exported by default: the user message, model output, tool arguments (commands, file paths, search queries), tool results, error text.
- Never exported:
transcriptPath,artifactDirectoryPath,workspacePaths. The emitter never opens files named in a payload.
To include content, add FI_HOOK_CAPTURE_CONTENT=1 to the env file. In CLI 1.4.2 this adds tool arguments to the tool span and tool error text to its status message, not prompt text, model output or tool results. Each field is capped at 4096 characters, common key formats are replaced with [REDACTED], and the account name in home-directory paths becomes <user>. See Privacy.
Troubleshooting
No trace appears.
- Check that the hook is listed and enabled:
/hooksin the CLI. The CLI also logsloaded 1 named hooks from 1 hooks.json file(s)in~/.gemini/antigravity-cli/log/;0means it did not find.agents/hooks.json(startagyinside the project). - Set
FI_HOOK_LOG_FILE=/tmp/fi-hook.login~/.config/fi-hook-to-otlp/envand send another message. No lines: Antigravity did not run the command, or the emitter found noFI_API_KEY/FI_SECRET_KEY(check the env file).error=lines: the collector is unreachable.http=401/403: wrong keys. python3must resolve for Antigravity. Use an absolute interpreter path such as/opt/homebrew/bin/python3inhooks.jsonif it does not.
Spans appear but no turn. Hooks were installed in the middle of a conversation, after its first model call. New messages create turns.
Events from the Antigravity app are missing. That is intended; this setup is for the CLI only.
A tool call is missing. If Antigravity rejected the call before running it (see What is not available), no hook fired for it.
The agent feels slower. Hooks run synchronously and block the agent loop. Each hook starts a Python process, and a tool step fires up to three of them (PreInvocation, PostToolUse, PostInvocation). On macOS and Linux the HTTP send runs in a detached background process, so FI_HOOK_TIMEOUT does not add to that.
Uninstall
Remove the "fi-hook-to-otlp" member from .agents/hooks.json (or delete the file if it holds nothing else) first, then the emitter, so Antigravity never runs a hook whose emitter is gone:
rm -r /path/to/your/project/.agents/fi-hook-to-otlp
The guarded commands make the reverse order safe too: a hook whose emitter is missing exits 0 and does nothing.
Questions & Discussion