Why this matters
The TUI is how you learn. It is not how CI, an editor, or a scheduled job talks to Grok Build. Those surfaces have different contracts. If you use the wrong one, you will wait for a permission prompt that nobody will click, or you will paste TUI folklore into a JSON-RPC stream and wonder why the process hangs.
Specialist rung. If you do not write software or run agents, you can stop after C4 and still have reached repo level.
Three names, three jobs:
| Name | What it is | When you use it |
|---|
Headless (grok -p) | One prompt, tools run, process prints and exits | Scripts, CI, a hook that needs a single answer |
ACP (grok agent stdio / serve) | Long-lived agent. Clients speak JSON-RPC | IDEs, SDKs, a custom app that streams turns |
| Grok Bot | A different product. Named teammates on a persistent cloud computer | Always-on work in apps and sites, not this CLI |
This module is the first two as Grok Build, and the third only so you stop mixing them. The artifact is a real grok -p JSON object with a sessionId you resumed, plus a short note Grok Build wrote that uses the words JSON-RPC and “shared cloud computer” correctly.
Teach
Headless: grok -p
grok -p "What files are in this directory? Do not edit."
grok -p "Explain the architecture" --output-format json
grok -p "Explain the architecture" --output-format streaming-json
-p / --single is the usual trigger. --prompt-file and --prompt-json also start headless. The process has full tools. It exits when the turn is done. Exit 0 is success, 1 is an error, 130 is Ctrl+C, 143 is SIGTERM.
Headless does not read piped stdin as the prompt. This fails as a brief:
git diff --staged | grok -p "write a commit message"
Do this instead:
grok -p "Write a concise commit message for:`n`n$(git diff --staged)"
grok --prompt-file .\prompt.txt
Output formats:
plain (default) — text on stdout
json — one object at the end: text, stopReason, sessionId, spend fields when the prompt reached the model
streaming-json — one typed event per line (text, tool_call, end, …). Switch on type. end is last.
streaming-messages-json — Messages-API shaped stream for clients that already parse that wire
Automation reads json or streaming-json. Keep stdout clean: --no-auto-update, and do not print your own logs to stdout.
Sessions. Each grok -p is a new session by default. Carry context with:
# capture id
grok -p "Remember the practice word is cedar." --output-format json
# resume THAT id — scripts should pass the id, not a title
grok -p "What was the practice word?" --resume "<sessionId>" --output-format json
# most recent session in this directory
grok -p "Continue." -c
-s / --session-id creates a new session with a UUID. It errors if the id exists or is not a UUID. It does not resume. That old upsert behavior is gone. -s with -r/-c only when you also pass --fork-session.
Other flags you will actually set:
| Flag | Role |
|---|
--cwd <path> | Working directory. Grok walks up to .git from here. Point at the subproject, not a giant monorepo root, if startup is slow. |
--always-approve | Alias --yolo, same as --permission-mode bypassPermissions. No interactive permission prompts. Deny rules and hooks still apply. |
--allow / --deny | Repeatable. Deny wins. |
--tools / --disallowed-tools | Headless only. Allowlist / denylist of built-in tool ids (run_terminal_cmd, not “bash”). |
--max-turns <n> | Headless only. Stop after N agentic turns. |
--worktree=name | Run the one-shot in a new git worktree (C8). |
CI without a browser: XAI_API_KEY from console.x.ai, or grok login --device-auth on that machine once.
--always-approve is for unattended runs. It is not “turn the safety off.” C10 is the rest of that sentence. Do not add it to a TUI habit because a prompt annoyed you.
ACP is JSON-RPC
ACP (Agent Client Protocol) is how an editor or your own process talks to a long-lived Grok Build agent. The wire is JSON-RPC 2.0. stdio is the common local path: newline-delimited JSON on stdin/stdout.
grok agent --always-approve stdio
grok agent --always-approve serve --bind 127.0.0.1:2419 --secret <token>
serve is a WebSocket server you run. Grok’s hosted cloud sandboxes do not run grok agent serve.
Lifecycle:
- Client →
initialize (protocol version, client capabilities)
- Client →
session/new (cwd, mcpServers, optional _meta.yoloMode)
- Client →
session/prompt (sessionId, prompt: [{ type: "text", text: "…" }])
- Agent →
session/update notifications (agent_message_chunk, agent_thought_chunk, tool_call, tool_call_update, plan)
- Permissions, unless always-approve /
yoloMode
session/prompt returning is not the same as “the answer is in result.” The assistant text arrives as session/update chunks. If you only read the RPC result, you will think the agent said nothing.
Grok adds x.ai/* extension methods (fs, git, worktree apply, terminal, session fork). Treat that list as non-exhaustive; discover methods from initialize. Official client SDKs exist (TypeScript @agentclientprotocol/sdk, Rust, Python, Go, Kotlin). Compatible editors include Zed, Neovim, Emacs. JetBrains is listed as coming.
Headless grok -p is the wrong tool for an IDE. ACP is the wrong tool for a one-line CI review. Pick once.
Grok Bot is not Grok Build
Grok Bot is a sibling product: named, persistent teammates. You message a Bot like a colleague. Bots have a cloud computer (browser, filesystem, terminal), connectors/MCP where available, and computer-use for sites without a clean API.
All of your Bots share one cloud computer, isolated to the account, not to each Bot. A login or file on that machine is available to every Bot you have. Separate Bots are not a security boundary. Each Bot has its own screen so they can drive tools in parallel.
That is not grok -p. It is not grok agent stdio. It does not share this pathway’s session directory. Do not file a Bot transcript as a C9 headless artifact.
If you have Bot access, the safety habits still look like C10: require approval for send, publish, purchase, delete, production. Enter passwords yourself on the computer; do not paste them into chat. If you do not have Bot access, you still need the distinction written down so you do not chase the wrong docs.
Worked example
Practice repo.
Set-Location $env:USERPROFILE\grok-build-practice
grok -p "Count the top-level files in this directory. Reply with one integer and the names. Do not edit." --output-format json --no-auto-update
Save the sessionId. Then:
grok -p "What integer did you report last turn? Reply with only that integer." --resume "<paste-id>" --output-format json --no-auto-update
The second text should be that integer. If it is not, you resumed the wrong id, or you used -s thinking it would resume.
Optional ACP smoke (you are not building an IDE today):
grok agent --help
You should see stdio and serve. A real handshake is initialize then session/new — JSON-RPC objects with "jsonrpc":"2.0", "id", "method". You do not type those into the TUI prompt.
Then open the TUI in the same repo and have Grok write notes/c9-surfaces.md from your session id and the three-row table in this lesson. It must say ACP is JSON-RPC and Grok Bot shares one cloud computer per account.
Your turn
Do c9-l01-e1 in Grok Build: a headless JSON run you resume, then a TUI-written distinction note. grok.com does not count. A Bot chat does not count.
Common failure
Piping stdin into -p. The diff never arrives. Use $(git diff) or --prompt-file.
-s to continue. You create a collision or a fresh session and lose the word you stored. -r / --resume with the id from JSON.
Always-approve as a lifestyle. You add --yolo to every TUI launch. Unattended automation only, with deny rules you already wrote. Next module is that control plane.
Calling Bot “headless Grok.” Different product, different computer, shared across every Bot on the account. If the file lives on the Bot VM, every Bot can see it.
ACP client that ignores session/update. You print result and get an empty string. Stream the chunks.
What’s next
C10 is permissions and review: ask versus always-approve, deny still winning, sandbox on Linux/macOS, and the review habit you will use in the capstone. No shortcuts around those gates.