# Grok
Grok is xAI's official terminal coding agent — the `grok` binary, also branded
**Grok Build**. This is the CLI txcript parses, not the unaffiliated community
`superagent-ai/grok-cli`: the disambiguators are the `~/.grok/sessions` store,
the ACP-style display log, and `"agent_name": "cursor"` in its own session
metadata (its toolset and harness lineage are Cursor's). xAI open-sourced Grok
Build in July 2026 under Apache 2.0 (a large Rust workspace at
`xai-org/grok-build`; issues and external contributions are closed). txcript's
mapping was reverse-engineered from real sessions written by grok 0.2.114 on
this machine; the upstream source now exists as a corroborating spec, and the
persistence module cited below matches the observed layout.
```
~/.grok/sessions/ (or $GROK_HOME/sessions)
└── <percent-encoded-cwd>/ one dir per workspace
├── prompt_history.jsonl (project-level; ignored)
└── <session-uuid>/ one dir per session
├── chat_history.jsonl ── model-protocol log ┐ the conversation
├── updates.jsonl ── ACP display log ┘ (two views of it)
├── summary.json ── session metadata
└── sidecars: events.jsonl, rewind_points.jsonl,
prompt_context.json, resources_state.json, signals.json,
system_prompt.txt (carried verbatim, not interpreted)
terminal/, assets/, *.lock (not carried)
```
## On disk
A session is a **directory of plain files**, not a single transcript file. The
root is `~/.grok/sessions`, overridden wholesale by `$GROK_HOME` (sessions then
live at `$GROK_HOME/sessions`). The first level is the workspace path,
URL-percent-encoded per byte (RFC 3986 unreserved characters kept); the second
is a session UUID. txcript's discovery walks exactly those two levels and
sniffs a session by the presence of `updates.jsonl` or `chat_history.jsonl` —
stray files like the project-level `prompt_history.jsonl` and the
`session_search.sqlite` index at the root are skipped. When `summary.json`
alone answers every metadata question (id, cwd, `created_at`, model),
discovery never opens the logs.
## Dissection of a transcript
The conversation exists twice: `chat_history.jsonl` is what the model sees on
continuation, `updates.jsonl` is what `grok --resume` replays on screen.
txcript reads the conversation from the model log and backfills from the
display log what the model log lacks — timestamps, user images, tool failure
status, and stop reasons.
| `chat_history.jsonl` line, `type: "user"` | Prompt text blocks, wrapped in `<user_query>` tags; optional `prior_turn_interrupt` | `Role::User` message of `Text` blocks (wrapper stripped), plus `Image` blocks pulled from the display log |
| `type: "assistant"` | Response text plus `tool_calls` (each with `id`, `name`, `arguments` as JSON-in-a-string), `model_id` | `Role::Assistant` message: `Text` + `ToolUse` blocks; `model` |
| `type: "reasoning"` | `summary` (array of `summary_text` parts) plus opaque `encrypted_content` | Assistant `Thinking` block; the encrypted token is preserved for round-trips |
| `type: "tool_result"` | `tool_call_id` plus (usually string) `content` | `ToolResult` block on a `Role::User` message, paired by id |
| `type: "system"` | The injected system prompt | Dropped from the conversation (kept in the native body) |
| `updates.jsonl` `user_message_chunk` | ACP prompt chunk — text, or `{type: "image", data, mimeType}`; grouped by `_meta.promptIndex` | Prompt timestamp; images (their **only** home — the model log holds a textual description instead) |
| `agent_message_chunk`, `agent_thought_chunk` | Streamed display text | Timestamps for text/thinking messages |
| `tool_call`, `tool_call_update` | Display-side call lifecycle (`status`: pending → completed/failed) | Tool timestamps; `status: "failed"` sets `is_error` on the result |
| `turn_completed` (method `_x.ai/session/update`) | End-of-turn marker with `stop_reason` | `StopReason` on the turn's last assistant message (`cancelled` → `Aborted`; `prior_turn_interrupt` is the fallback) |
| `summary.json` | Session metadata: `info.id`, `info.cwd`, `created_at`, `generated_title`, `current_model_id`, `head_branch`, git info | `Meta` |
Threading is strictly linear — no parent pointers, no branching; a turn opens
at each real user prompt and closes at the next one. Tool calls pair with
results by `tool_call_id` alone. Grok's injected `<user_info>` preamble (a
`user` record) is context, not a prompt: it is skipped, though its
`Workspace Path:` line serves as a cwd fallback when `summary.json` is absent.
Tool names are Cursor's and get normalized to the Claude convention
(`Shell`→`Bash`, `StrReplace`→`Edit`, `path`→`file_path`, and so on).
A synthetic `chat_history.jsonl` assistant line, shaped like the real thing:
```json
{"type": "assistant", "content": "Reading the config first.",
"tool_calls": [{"id": "call_a1b2", "name": "Read",
"arguments": "{\"path\": \"/tmp/demo/config.toml\"}"}],
"model_id": "grok-composer-2.5-fast", "model_fingerprint": "fp_0000"}
```
## Caveats
- Observations are from grok 0.2.114 (`chat_format_version: 1`).
- The model log carries **no timestamps**. Every date comes from the display
log's `_meta.agentTimestampMs` (or the line-level epoch `timestamp`); a
session missing `updates.jsonl` — they exist in the wild — parses fine but
every message falls back to the session timestamp.
- User images live only in the display log; natively the model log holds a
Grok-generated description. Regeneration writes an `[image]` placeholder
there, since the description can't be synthesized.
- Tool-call `arguments` are JSON-in-a-string; unparseable strings are kept
raw rather than dropped. `block_until_ms` arrives as a float (`120000.0`)
and is converted to integral `timeout_ms` only when lossless.
- Token usage is never extracted (the display log's `_meta.totalTokens` is
ignored), so `usage` is always absent.
- Regenerating from Common is lossy: per-turn usage, non-turn stop reasons,
reasoning record ids, structured tool-result JSON (flattened to strings),
and the system prompt record are not reproduced; sidecars are left empty
for Grok to regenerate. Native load → save round-trips everything except
`terminal/` output scratch and `*.lock` files, which are deliberately
not carried.
- Hostile input is bounded: `_meta.promptIndex` is untrusted and sizes an
allocation, so implausible values are treated as absent; delete refuses
any path that doesn't resolve to `<root>/<project>/<id>`.
## References
- Upstream (open source, Apache 2.0), pinned:
<https://github.com/xai-org/grok-build/blob/b13fa526f5112c0b20dad5f1f2300d3d3b127895/crates/codegen/xai-chat-state/src/persistence.rs>
— session persistence in `xai-chat-state`; path verified at that SHA via the
GitHub API on 2026-08-10.
- This document was reverse-engineered from sessions observed under
`~/.grok/sessions` (grok 0.2.114) before the upstream release; the code
above corroborates it but was not the primary source.
- Authoritative mapping: `src/harness/grok.rs` (module docs and code) and
`tests/integration/grok.rs` (fixtures shaped like real sessions).
Last verified: 2026-08-10, against src/harness/grok.rs and real local sessions.