Grok Build session tracing with Future AGI

Trace xAI Grok Build (grok CLI and TUI) sessions in Future AGI with Grok hooks: one trace per session, with turns, tool calls, subagents, timing, and errors. Prompt and tool content stay off by default.

Trace Grok Build sessions, turns, tool calls, and errors in Future AGI using Grok’s hooks and the shared hook-to-OTLP emitter.

Grok Build 1.0.52’s native external OpenTelemetry stream (GROK_EXTERNAL_OTEL) sends logs and metrics, not traces. Use the hook integration below for session traces; the native stream cannot be sent to the Future AGI trace endpoint.

Grok Build session                one grok session (sessionId)
└── Grok Build turn               one prompt (promptId): stop reason, status
    ├── list_dir                  tool call (toolUseId): duration, status
    ├── read_file
    ├── spawn_subagent
    └── Subagent: general-purpose subagent launch, linked to the subagent's own trace

A subagent runs as its own grok session, so it becomes its own trace (Grok Build subagent session: <type>) with its own turn and tool spans. The parent’s subagent span carries the child’s session id in grok_build.subagent.id.

This traces the Grok Build application, not calls from your own code to the xAI API. Grok Bot and the Grok model inside Cursor do not run these hooks.

Prerequisites

  • Grok Build 1.0.52 on macOS (Apple silicon), with a working model configuration. This recipe uses POSIX shell commands; Linux, WSL, and native Windows are outside its supported scope.
  • Python 3.9+ on the PATH Grok uses, and Bash for the installer. No Python packages are required.
  • Future AGI API credentials.

1. Install the hook

Use the shared hook-to-OTLP installer, the same integration path as Cursor. It installs the emitter and Grok adapter together into ~/.grok/fi-hook-to-otlp/ and writes ~/.grok/hooks/futureagi.json. You do not need to copy or edit exporter source. If the hook file already exists, the installer preserves it and prints the entries to compare and merge.

git clone --depth 1 https://github.com/future-agi/docs.git fi-docs
fi-docs/examples/coding-agents/grok-build/install.sh

To update later, pull the latest examples with git -C fi-docs pull --ff-only and rerun the installer. This updates the shared emitter and adapter together while preserving your hook file.

The installer writes this hook configuration:

{
  "hooks": {
    "SessionStart":       [{ "hooks": [{ "type": "command", "command": "(python3 -B \"$HOME/.grok/fi-hook-to-otlp/emit.py\" grok_build SessionStart 2>/dev/null || true)", "timeout": 5 }] }],
    "UserPromptSubmit":   [{ "hooks": [{ "type": "command", "command": "(python3 -B \"$HOME/.grok/fi-hook-to-otlp/emit.py\" grok_build UserPromptSubmit 2>/dev/null || true)", "timeout": 5 }] }],
    "PostToolUse":        [{ "hooks": [{ "type": "command", "command": "(python3 -B \"$HOME/.grok/fi-hook-to-otlp/emit.py\" grok_build PostToolUse 2>/dev/null || true)", "timeout": 5 }] }],
    "PostToolUseFailure": [{ "hooks": [{ "type": "command", "command": "(python3 -B \"$HOME/.grok/fi-hook-to-otlp/emit.py\" grok_build PostToolUseFailure 2>/dev/null || true)", "timeout": 5 }] }],
    "PermissionDenied":   [{ "hooks": [{ "type": "command", "command": "(python3 -B \"$HOME/.grok/fi-hook-to-otlp/emit.py\" grok_build PermissionDenied 2>/dev/null || true)", "timeout": 5 }] }],
    "Stop":               [{ "hooks": [{ "type": "command", "command": "(python3 -B \"$HOME/.grok/fi-hook-to-otlp/emit.py\" grok_build Stop 2>/dev/null || true)", "timeout": 5 }] }],
    "StopFailure":        [{ "hooks": [{ "type": "command", "command": "(python3 -B \"$HOME/.grok/fi-hook-to-otlp/emit.py\" grok_build StopFailure 2>/dev/null || true)", "timeout": 5 }] }],
    "StopCancelled":      [{ "hooks": [{ "type": "command", "command": "(python3 -B \"$HOME/.grok/fi-hook-to-otlp/emit.py\" grok_build StopCancelled 2>/dev/null || true)", "timeout": 5 }] }],
    "SubagentStart":      [{ "hooks": [{ "type": "command", "command": "(python3 -B \"$HOME/.grok/fi-hook-to-otlp/emit.py\" grok_build SubagentStart 2>/dev/null || true)", "timeout": 5 }] }],
    "SubagentStop":       [{ "hooks": [{ "type": "command", "command": "(python3 -B \"$HOME/.grok/fi-hook-to-otlp/emit.py\" grok_build SubagentStop 2>/dev/null || true)", "timeout": 5 }] }],
    "PostCompact":        [{ "hooks": [{ "type": "command", "command": "(python3 -B \"$HOME/.grok/fi-hook-to-otlp/emit.py\" grok_build PostCompact 2>/dev/null || true)", "timeout": 5 }] }],
    "SessionEnd":         [{ "hooks": [{ "type": "command", "command": "(python3 -B \"$HOME/.grok/fi-hook-to-otlp/emit.py\" grok_build SessionEnd 2>/dev/null || true)", "timeout": 5 }] }]
  }
}

Check that grok loaded it: grok inspect lists the 12 hooks with source user. In the TUI, /hooks shows the same list.

Note

Keep the parentheses and 2>/dev/null || true guard in each command. Grok passes JSON on stdin through sh -c; the guard returns exit 0 even when Python or the emitter is missing. Without it, exit 2 from UserPromptSubmit blocks the prompt. The installed emitter answers {} (no decision), and -B prevents bytecode files.

PreToolUse is not registered, so tracing does not participate in tool approval. Tool spans use Grok’s PostToolUse duration (durationMs), rather than timing hook processes.

Coexisting with Cursor and Claude Code hooks

Install this integration only in ~/.grok/hooks/. Grok also reads Cursor and Claude Code hook files. In Grok 1.0.52, Cursor’s flat hook entries do not run and appear as load errors in grok inspect; matcher-style entries can still run.

To clear Cursor’s flat-entry errors, optionally set this in ~/.grok/config.toml:

[compat.cursor]
hooks = false

This disables all Cursor-file hooks inside Grok, including your own matcher groups; it does not change Cursor itself. Leave [compat.claude] enabled so Grok still runs any guard or deny hooks in your Claude Code settings. To disable one loaded hook instead, select it in /hooks and press Space. Cursor flat entries have no selectable hook row, so they require the setting above.

2. Add your Future AGI keys

Put the keys in the emitter’s env file, so they work however Grok is started. If you already use this file for another coding agent, edit it rather than overwriting it; its settings are shared.

umask 077
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=grok-build-sessions
EOF
chmod 600 ~/.config/fi-hook-to-otlp/env

Variables set in the environment grok runs in take precedence over the file. For a self-hosted Future AGI, also set FI_BASE_URL (traces go to $FI_BASE_URL/tracer/v1/traces). All settings are listed on the hook-to-OTLP page.

3. Run a session

Start grok in a repository and send one prompt that uses a tool, for example “List the files in this folder and read README.md”. Headless runs work the same way:

grok -p "List the files in this folder and read README.md"

Open Observe in Future AGI and select the project (grok-build-sessions above). The session appears as one trace named Grok Build session. Open it to see the turn and its tool spans. Later prompts in the same session are added to the same trace as new turns.

What is captured

Grok hookFuture AGI spanFields
SessionStartGrok Build session (root)session.id = sessionId, start source (new, or load for a resumed session, which restarts the session span; see What is not available), permission mode
UserPromptSubmitGrok Build turn startpromptId
PostToolUsetool span named after the tool (read_file, run_terminal_command, search_replace, <server>__<tool> for MCP)gen_ai.tool.name, gen_ai.tool.call.id (toolUseId), duration (durationMs), backgrounded flag, input- and result-truncated flags. For shell commands: exit code, timed-out flag, and signal; a non-zero exit, a timeout, or a kill by a signal marks the span ERROR (exit code N, timed out, signal N), so a grep with no match shows as an error too. A result grok truncated to text carries no exit code.
PostToolUseFailuresame tool span, status ERRORstatus message tool failed (dispatch failure or MCP error result)
PermissionDeniedsame tool span, status ERRORstatus message permission denied
StopGrok Build turn endstop reason (end_turn)
StopFailureturn end, status ERRORstatus message = classified error (rate_limit, authentication_failed, invalid_request, server_error, max_output_tokens, unknown)
StopCancelledturn end, status unsetcancel reason (user_interrupt, permission_rejected, max_turns, …), cancelledBy
SubagentStartSubagent: <type>subagent type (an agent name such as explore or my-plugin:reviewer; any other value reads subagent), grok_build.subagent.id (the subagent’s session id)
SubagentStopthe subagent session’s turn endsubagent type
PostCompactContext compactiontrigger (manual, auto)
SessionEndGrok Build session endend reason (can be lost on a slow collector; see What is not available)

Grok tool events carry no promptId, so each tool span is attached to the turn that is open in its session and carries coding_agent.turn.inferred=true. The Stop that grok fires at session teardown carries no promptId and is not treated as a turn end.

What is not available

  • Model name, token counts, and cost. Grok hook events do not include them.
  • Model request spans. There is no hook per model call.
  • A single trace for a session and its subagents. Subagent events carry the subagent’s own session id and type, not the parent session id, so each subagent is its own trace, linked from the parent by grok_build.subagent.id.
  • Bash-mode (!) commands and builtin slash commands. Grok fires no hooks for them, so they are not in the trace. An interrupted one reports StopCancelled for a prompt that never started, which appears as an empty, zero-length turn.
  • A reliable session end reason. The session span is re-sent with every batch, and batches are sent in parallel, so when the collector is slow an earlier batch can arrive last and its copy of the session span (without the end reason) wins.
  • One start time for a resumed session. A session reopened with --resume, --continue, or /resume (SessionStart with start source load) keeps its trace, but the session span restarts at the resume time: its start and duration then cover only the resumed part, while the earlier turns stay in the trace with their own times.
  • Notification and PreCompact events are not mapped.
  • Grok Build features outside hooks: grok export Markdown transcripts, ACP (grok agent stdio), the session store under ~/.grok/sessions, and Grok’s own trace upload are not read.

Privacy

By default only structure and metadata leave your machine: session, prompt and tool-call ids, tool names, subagent types, permission mode, durations, stop reasons, statuses, and a shell command’s exit code. Status messages are fixed strings or grok’s own error types (rate_limit, …), never error text.

  • Not exported by default: prompt text, tool input (toolInput), tool results (toolResult, except a shell command’s exit code, timed-out flag, and signal), the final assistant message, subagent task descriptions, and error details.
  • Never exported as metadata: cwd, workspaceRoot, and transcriptPath (the emitter never opens the transcript).

To include content, add FI_HOOK_CAPTURE_CONTENT=1 to the env file. The prompt becomes the turn’s input.value, the final assistant message its output.value, tool input and results go on the tool span, and error details (errorDetails, a failed tool’s error, a denial reason) become the status message and exception.message. Tool input and results contain absolute file paths (and, for shell commands, the working directory), so with content on, paths do leave your machine inside those fields, with the user name in home-directory paths replaced by <user> (/Users/<user>/project/README.md). Each field is capped at 4096 characters and common key formats are replaced with [REDACTED]. See Privacy for details.

Troubleshooting

No trace appears.

  1. Run grok inspect in the project. The futureagi.json hooks must be listed. If they are missing, check that the file is in ~/.grok/hooks/ and is valid JSON.
  2. Set FI_HOOK_LOG_FILE=/tmp/fi-hook.log in ~/.config/fi-hook-to-otlp/env and run another prompt. No lines: grok did not run the command (check python3 and the path in the hook file). error= lines: the collector is unreachable. http=401/403: wrong keys.
  3. Confirm python3 --version reports 3.9 or later, and that ~/.grok/fi-hook-to-otlp/emit.py exists. If the emitter is missing, rerun the installer; keep the guard in the hook commands.

Tools appear directly under the session. Tool events carry no promptId; they attach to the turn that is open in that session. A tool that runs while no turn is open (for example a background task finishing after the turn ended, or a continuation round after another Stop hook kept the agent working) is attached to the session.

A turn is marked incomplete. The session ended before grok sent Stop, StopFailure, or StopCancelled for it. Grok drops turn-end reports still queued when it exits; the turn is closed at session end with coding_agent.incomplete=true.

Each subagent is a separate trace. That is expected; see What is not available.

Uninstall

Delete ~/.grok/hooks/futureagi.json first, then ~/.grok/fi-hook-to-otlp/. To pause tracing without removing anything, set FI_HOOK_DISABLED=1 in the env file.

Next steps

  • Hook-to-OTLP emitter: span model, configuration, and privacy details shared by all hook-based coding agents.
  • Cursor: the same emitter for Cursor Agent sessions.
Was this page helpful?

Questions & Discussion