driver: grok
ref: (this issue)
worktrees: (not yet provisioned)
branches: (not yet)
port: (none — CLI lesson, no dev server)
How to start (fresh session only)
Do not implement from the planning session that filed this. New Grok Build session, repo root.
tirocine does not yet have .grok/skills/xmission/. Claude /mission is not the contract. Fetch the Grok contract from rdtiv/1269j .grok/skills/xmission/SKILL.md and follow it.
First package of this kickoff: port a thin .grok/ into tirocine (xmission skill + dual-driver rule + scripts/landed.sh). Drop 1269j’s operator-exec.md and site-law.md. Add the namespace table to CLAUDE.md. Then provision:
.grok/worktrees/wt-<this#>-grok-lesson
branch: x/feat/grok-transfer
base: origin/main
Issue #1 / PR #2 is the Claude Python mission (.claude/worktrees/wt-1-python-build/). Do not touch that worktree. Namespaces:
| Driver |
Command |
Worktrees |
Branches |
Ports |
| Claude |
/mission |
.claude/worktrees/ |
house scheme |
3000–3999 |
| Grok |
/xmission |
.grok/worktrees/ |
x/<type>/<slug> |
4000–4999 |
This lesson has no web server; record port: none.
Grok does not reliably expand $ARGUMENTS. This issue body is the source of truth.
Goal
A developing programmer who finished docs/typescript.md rebuilds the same weather assistant against Grok and can point at every difference. Which ideas were Claude, and which were real.
Python (issue #1) remains “which ideas were TypeScript?” This document stays in TypeScript on purpose.
Not a 13-part Grok clone. Not a second npm package. Not the app / AI SDK chapter.
Researched 2026-08-14 against docs.x.ai and rdtiv/1269j.
Decisions (locked)
| Decision |
Choice |
Why |
| Layout |
src/grok-*.ts next to the Claude files |
Same cwd, same .env, same tsx --env-file=.env. Split-pane comparison. |
| Package |
One package.json, one tsconfig.json |
Root grows openai. Claude-only readers install it and never import it. |
.env |
One file at repo root; add XAI_API_KEY |
Never --env-file=../.env. Never copy keys. |
| Run |
Always from repo root |
"grok:agent": "tsx --env-file=.env src/grok-agent.ts" |
| SDK |
openai + Responses API |
xAI’s preferred JS path. baseURL: 'https://api.x.ai/v1' is the first beat. |
| Not used |
@ai-sdk/xai, Chat Completions as the main path |
AI SDK hides the tool loop (docs/app.md). Chat Completions is a footnote. |
| Shared |
src/weather.ts, src/usage.ts, src/usage-report.ts |
Weather was never Anthropic. One ledger, both vendors. |
| Not shared |
src/text.ts, src/config.ts |
Claude-typed. Grok gets grok-text.ts and grok-config.ts. |
| Series slot |
New document 3; Python becomes 4; app becomes 5 |
Model-switch before language-switch. |
August 2026 research (re-check before pinning prices or IDs)
Use these
- Model:
grok-4.6 (docs: default for code). Pin like claude-sonnet-5. Knowledge cutoff 2026-02-01.
- Key / host:
XAI_API_KEY, https://api.x.ai/v1, console.x.ai.
- First call:
const client = new OpenAI({
apiKey: process.env.XAI_API_KEY,
baseURL: 'https://api.x.ai/v1',
});
const response = await client.responses.create({
model: 'grok-4.6',
input: '...',
});
console.log(response.output_text);
- Prices (grok-4.6, prompt < 200k): $2.00 / 1M input, $0.50 / 1M cached input, $6.00 / 1M output. At ≥200k the entire request doubles. Implement the cheap tier; comment the threshold; date
Verified 2026-08-14. Sources: models, pricing.
- Responses usage:
input_tokens, output_tokens, input_tokens_details.cached_tokens, output_tokens_details.reasoning_tokens.
- Caching is automatic. No
cache_control, no 1.25× write, no 1,024 minimum. Responses sticky key: prompt_cache_key via extra_body (official JS uses @ts-expect-error — do not leave that in a beginner file). prompt caching.
- Memory fork: server store 30 days by default;
store: false is local-only; previous_response_id continues a stored turn. generate text.
- Client tools:
{ type: "function", name, description, parameters }. Output item function_call, arguments is a JSON string, call_id. Return { type: "function_call_output", call_id, output }. function calling.
- Server-side search:
{ type: "web_search" }. xAI runs it. No runTool. Citations possible. $5 / 1k invocations + tokens. web search. First-class file, not a footnote.
- Structured output: same Zod schema, different envelope. structured outputs.
Not 1:1 with Claude
| Claude |
Grok |
cache_control + 1.25× / 0.1× |
Automatic prefix cache; $0.50 vs $2.00 = 0.25×, no write premium |
input_tokens is uncached remainder |
Responses input_tokens is documented as the full prompt; cached_tokens is a subset. Adapter must subtract. Confirm with one live call. |
| You always resend history |
Optional |
| Model never executes anything |
True of your functions. False of web_search. |
max_tokens required |
Optional |
Confirm live before locking
- Streaming + Responses on
api.x.ai. Official streaming JS is Chat Completions. openai supports responses.create({ stream: true }). Try Responses first; if it fails, Chat Completions and one honest sentence. Do not teach both as equals.
responses.parse + Zod vs text.format + parse. Same WeatherRequest either way.
prompt_cache_key types — extra_body, no @ts-expect-error in lesson files.
openai version — add current; typecheck is the pin.
web_search output item names + citation field — print what the live API returns.
Files the reader builds
| File |
Claude twin |
Teaches |
src/grok-config.ts |
config.ts |
MODEL = 'grok-4.6' |
src/grok-text.ts |
text.ts |
output is typed items; output_text vanishes on function_call |
src/grok-index.ts |
index.ts |
Client + baseURL; print raw object, then text |
src/grok-chat.ts |
chat.ts |
store: false + local array, then previous_response_id |
src/grok-parse.ts |
parse-request.ts |
Identical WeatherRequest Zod object |
src/grok-agent.ts |
agent.ts |
Hand-written loop; same getWeather; JSON.parse(arguments) |
src/grok-search.ts |
(none) |
web_search alone, then web_search + get_weather |
src/grok-assistant.ts |
assistant.ts |
Finished program. Local weather only. |
src/grok-stream.ts |
stream.ts |
Short coda. Path from live probe. |
src/grok-injection.ts |
injection.ts |
Same POISON, same moral. Not a fix. |
src/grok-models.ts |
models.ts |
Documented extra. |
No files for: truncate (one paragraph), bench, weather-test, a second weather client, a Grok usage-report, retries as a part (timeout: 360_000 comment only).
Mapping table (top of docs/grok.md)
| Idea |
Claude |
Grok (Responses) |
| Client |
new Anthropic() |
new OpenAI({ apiKey, baseURL: 'https://api.x.ai/v1' }) |
| Key |
ANTHROPIC_API_KEY |
XAI_API_KEY |
| The call |
messages.create |
responses.create |
| What you send |
messages + top-level system |
input (system is an item) |
| The reply |
content[] |
output[] (message, function_call, reasoning) |
| The text |
textFrom() |
output_text or grok-text.ts |
| Memory |
Resend everything |
Resend or previous_response_id |
| Wants a tool |
stop_reason === 'tool_use' |
output item type === 'function_call' |
| Tool request |
tool_use + input object |
function_call + arguments string |
| Tool result |
user tool_result + tool_use_id |
function_call_output + call_id |
| Schema field |
input_schema |
parameters |
| Who runs it |
Always you |
You (function) or xAI (web_search) |
| Structured out |
messages.parse + zodOutputFormat |
Responses format — same Zod |
| Cache |
explicit marker, 1.25× / 0.1× |
automatic; prompt_cache_key; $0.50 vs $2.00 |
If a difference is not in this table, the table is wrong.
Two ideas that are actually new
- Someone still has to remember. It does not have to be you.
store: false first, then previous_response_id. Watch usage in both modes.
- Some tools run on their servers. Full section +
src/grok-search.ts.
grok-search.ts
- Search only:
tools: [{ type: "web_search" }]. Current headline, not weather. One responses.create. Print raw output, text, citations. No while loop.
- Both tools:
web_search + same get_weather schema. Question that wants both. Loop fires only for function_call.
Do not put web_search on grok-assistant.ts. No image search / image understanding / x_search. Do not add the $5 / 1k fee to usage.csv; say the row is missing it.
Ledger
Do not fork usage.csv. Do not change the 15 columns.
type LedgerUsage = {
input_tokens: number; // uncached remainder (keep Claude’s convention)
cache_read: number;
cache_write: number; // always 0 for Grok
thinking_tokens: number;
output_tokens: number;
};
fromAnthropic — current math, extracted.
fromResponses — treat cached_tokens as a subset of input_tokens unless a live call proves otherwise.
- Grok
costOf: uncached * 2 + cached * 0.50 + output * 6 per million. Comment 200k doubling; do not implement it.
logCall stays Claude. Grok files call logGrokCall (no Anthropic types).
usage-report.ts: do not apply 0.1× / 1,024-token copy to mixed or Grok rows. Savings from cost_usd.
docs/grok.md shape
Transfer voice, not a second course. Type the examples. Open the Claude twin beside each file.
- What this is / is not / prerequisite / cost + spend limit
- Mapping table
- Key +
openai install
- First call
- Memory fork
- Same Zod schema
- Tool loop
- Who runs the tool (
grok-search.ts)
- Finished assistant (local weather only)
- Caching note (not Part 11)
- Streaming coda
- Injection coda
- Where this sits
Series nav on every existing series doc becomes: Setup → TypeScript (Claude) → Grok → Python → App. Python and app keep their gap banners.
Fence markers: same as typescript.md (// File — src/grok-agent.ts, etc.).
verify:docs
Today, adding grok-*.ts fails CI. Ownership:
| Files |
Owner |
Extra |
src/*.ts except grok-* |
docs/typescript.md |
src/models.ts |
src/grok-*.ts |
docs/grok.md |
src/grok-models.ts |
Run the extract → classify → compile staged → diff → coverage pipeline twice. openai must be a root dep before the first grok-*.ts lands.
Other required edits
package.json: openai; scripts grok, grok:chat, grok:parse, grok:agent, grok:search, grok:assistant, grok:stream, grok:injection, grok:models
.env.example: XAI_API_KEY — optional; Claude scripts must work without it
- README series + scripts tables; three keys (Grok optional)
CLAUDE.md: unprefixed = Claude, grok-* = Grok; fix stale cost.ts / logCost → usage.ts / logCall
- Setup docs: one short optional-third-key note
- CI commands unchanged; still keyless
Invariants
- Comments explain why, in the existing lesson voice.
- ESM imports keep the
.js extension.
- Do not merge Claude and Grok assistants.
- Do not present
BOUNDARY as a security control.
- Do not use the AI SDK in this document.
- A reader can open
src/agent.ts beside src/grok-agent.ts and point at every difference.
Out of scope
Python companion code (issue #1). The Next.js app. Workspaces / second package / grok/ folder. Tool-invocation fees in the CSV. web_search on the assistant. 200k price branch. A third provider. Runtime tests (optional keyless costOf check only).
Implementation order
- Port thin
.grok/ from 1269j (skill + dual-driver + landed.sh + CLAUDE.md table).
- Scaffold (keyless):
openai, ledger adapters, usage-report copy, check-docs two-document ownership, scripts, .env.example, series nav.
- Live probe (
XAI_API_KEY, not committed): usage semantics, store: false, function_call, streaming, web_search shape. Lock adapters. Delete scratch.
- Lesson files +
docs/grok.md together so verify:docs stays green.
- Surface docs (README, CLAUDE.md, setups).
- Verify:
npm run typecheck, npm run verify:docs; one live grok + grok:agent + grok:search if a key is present. Do not loop billed calls.
Acceptance
Success
A reader finishes the TypeScript build, runs npm run grok:assistant the same evening, opens src/assistant.ts beside src/grok-assistant.ts, and can explain the mapping table — including the two ideas that were not just renaming.
Notes
main is protected. PRs required.
- Tutorial code: clarity and pedagogical comments beat production abstraction.
docs/typescript.md is the model for structure, voice, and fence markers.
- Re-check docs.x.ai on the day you pin prices and model IDs.
How to start (fresh session only)
Do not implement from the planning session that filed this. New Grok Build session, repo root.
tirocinedoes not yet have.grok/skills/xmission/. Claude/missionis not the contract. Fetch the Grok contract fromrdtiv/1269j.grok/skills/xmission/SKILL.mdand follow it.First package of this kickoff: port a thin
.grok/into tirocine (xmissionskill +dual-driverrule +scripts/landed.sh). Drop 1269j’soperator-exec.mdandsite-law.md. Add the namespace table toCLAUDE.md. Then provision:Issue #1 / PR #2 is the Claude Python mission (
.claude/worktrees/wt-1-python-build/). Do not touch that worktree. Namespaces:/mission.claude/worktrees//xmission.grok/worktrees/x/<type>/<slug>This lesson has no web server; record
port: none.Grok does not reliably expand
$ARGUMENTS. This issue body is the source of truth.Goal
A developing programmer who finished
docs/typescript.mdrebuilds the same weather assistant against Grok and can point at every difference. Which ideas were Claude, and which were real.Python (issue #1) remains “which ideas were TypeScript?” This document stays in TypeScript on purpose.
Not a 13-part Grok clone. Not a second npm package. Not the app / AI SDK chapter.
Researched 2026-08-14 against docs.x.ai and
rdtiv/1269j.Decisions (locked)
src/grok-*.tsnext to the Claude files.env, sametsx --env-file=.env. Split-pane comparison.package.json, onetsconfig.jsonopenai. Claude-only readers install it and never import it..envXAI_API_KEY--env-file=../.env. Never copy keys."grok:agent": "tsx --env-file=.env src/grok-agent.ts"openai+ Responses APIbaseURL: 'https://api.x.ai/v1'is the first beat.@ai-sdk/xai, Chat Completions as the main pathdocs/app.md). Chat Completions is a footnote.src/weather.ts,src/usage.ts,src/usage-report.tssrc/text.ts,src/config.tsgrok-text.tsandgrok-config.ts.August 2026 research (re-check before pinning prices or IDs)
Use these
grok-4.6(docs: default for code). Pin likeclaude-sonnet-5. Knowledge cutoff 2026-02-01.XAI_API_KEY,https://api.x.ai/v1, console.x.ai.Verified 2026-08-14. Sources: models, pricing.input_tokens,output_tokens,input_tokens_details.cached_tokens,output_tokens_details.reasoning_tokens.cache_control, no 1.25× write, no 1,024 minimum. Responses sticky key:prompt_cache_keyviaextra_body(official JS uses@ts-expect-error— do not leave that in a beginner file). prompt caching.store: falseis local-only;previous_response_idcontinues a stored turn. generate text.{ type: "function", name, description, parameters }. Output itemfunction_call,argumentsis a JSON string,call_id. Return{ type: "function_call_output", call_id, output }. function calling.{ type: "web_search" }. xAI runs it. NorunTool. Citations possible. $5 / 1k invocations + tokens. web search. First-class file, not a footnote.Not 1:1 with Claude
cache_control+ 1.25× / 0.1×input_tokensis uncached remainderinput_tokensis documented as the full prompt;cached_tokensis a subset. Adapter must subtract. Confirm with one live call.web_search.max_tokensrequiredConfirm live before locking
api.x.ai. Official streaming JS is Chat Completions.openaisupportsresponses.create({ stream: true }). Try Responses first; if it fails, Chat Completions and one honest sentence. Do not teach both as equals.responses.parse+ Zod vstext.format+ parse. SameWeatherRequesteither way.prompt_cache_keytypes —extra_body, no@ts-expect-errorin lesson files.openaiversion — add current;typecheckis the pin.web_searchoutput item names + citation field — print what the live API returns.Files the reader builds
src/grok-config.tsconfig.tsMODEL = 'grok-4.6'src/grok-text.tstext.tsoutputis typed items;output_textvanishes onfunction_callsrc/grok-index.tsindex.tsbaseURL; print raw object, then textsrc/grok-chat.tschat.tsstore: false+ local array, thenprevious_response_idsrc/grok-parse.tsparse-request.tsWeatherRequestZod objectsrc/grok-agent.tsagent.tsgetWeather;JSON.parse(arguments)src/grok-search.tsweb_searchalone, thenweb_search+get_weathersrc/grok-assistant.tsassistant.tssrc/grok-stream.tsstream.tssrc/grok-injection.tsinjection.tsPOISON, same moral. Not a fix.src/grok-models.tsmodels.tsNo files for: truncate (one paragraph), bench, weather-test, a second weather client, a Grok usage-report, retries as a part (
timeout: 360_000comment only).Mapping table (top of
docs/grok.md)new Anthropic()new OpenAI({ apiKey, baseURL: 'https://api.x.ai/v1' })ANTHROPIC_API_KEYXAI_API_KEYmessages.createresponses.createmessages+ top-levelsysteminput(system is an item)content[]output[](message,function_call,reasoning)textFrom()output_textorgrok-text.tsprevious_response_idstop_reason === 'tool_use'outputitemtype === 'function_call'tool_use+inputobjectfunction_call+argumentsstringtool_result+tool_use_idfunction_call_output+call_idinput_schemaparametersfunction) or xAI (web_search)messages.parse+zodOutputFormatprompt_cache_key; $0.50 vs $2.00If a difference is not in this table, the table is wrong.
Two ideas that are actually new
store: falsefirst, thenprevious_response_id. Watchusagein both modes.src/grok-search.ts.grok-search.tstools: [{ type: "web_search" }]. Current headline, not weather. Oneresponses.create. Print rawoutput, text, citations. Nowhileloop.web_search+ sameget_weatherschema. Question that wants both. Loop fires only forfunction_call.Do not put
web_searchongrok-assistant.ts. No image search / image understanding /x_search. Do not add the $5 / 1k fee tousage.csv; say the row is missing it.Ledger
Do not fork
usage.csv. Do not change the 15 columns.fromAnthropic— current math, extracted.fromResponses— treatcached_tokensas a subset ofinput_tokensunless a live call proves otherwise.costOf:uncached * 2 + cached * 0.50 + output * 6per million. Comment 200k doubling; do not implement it.logCallstays Claude. Grok files calllogGrokCall(no Anthropic types).usage-report.ts: do not apply 0.1× / 1,024-token copy to mixed or Grok rows. Savings fromcost_usd.docs/grok.mdshapeTransfer voice, not a second course. Type the examples. Open the Claude twin beside each file.
openaiinstallgrok-search.ts)Series nav on every existing series doc becomes: Setup → TypeScript (Claude) → Grok → Python → App. Python and app keep their gap banners.
Fence markers: same as
typescript.md(// File — src/grok-agent.ts, etc.).verify:docsToday, adding
grok-*.tsfails CI. Ownership:src/*.tsexceptgrok-*docs/typescript.mdsrc/models.tssrc/grok-*.tsdocs/grok.mdsrc/grok-models.tsRun the extract → classify → compile staged → diff → coverage pipeline twice.
openaimust be a root dep before the firstgrok-*.tslands.Other required edits
package.json:openai; scriptsgrok,grok:chat,grok:parse,grok:agent,grok:search,grok:assistant,grok:stream,grok:injection,grok:models.env.example:XAI_API_KEY— optional; Claude scripts must work without itCLAUDE.md: unprefixed = Claude,grok-*= Grok; fix stalecost.ts/logCost→usage.ts/logCallInvariants
.jsextension.BOUNDARYas a security control.src/agent.tsbesidesrc/grok-agent.tsand point at every difference.Out of scope
Python companion code (issue #1). The Next.js app. Workspaces / second package /
grok/folder. Tool-invocation fees in the CSV.web_searchon the assistant. 200k price branch. A third provider. Runtime tests (optional keylesscostOfcheck only).Implementation order
.grok/from 1269j (skill + dual-driver +landed.sh+ CLAUDE.md table).openai, ledger adapters,usage-reportcopy,check-docstwo-document ownership, scripts,.env.example, series nav.XAI_API_KEY, not committed): usage semantics,store: false,function_call, streaming,web_searchshape. Lock adapters. Delete scratch.docs/grok.mdtogether soverify:docsstays green.npm run typecheck,npm run verify:docs; one livegrok+grok:agent+grok:searchif a key is present. Do not loop billed calls.Acceptance
.grok/exists;/xmissionis the Grok contract; Claude worktrees untouchedsrc/grok-*.tsexist and are built bydocs/grok.mdnpm run verify:docscovers both documents; CI green on 20.x and 22.x, still keylessusage.csv; Grok rowscache_write0; ClaudelogCallunchangedWeatherRequestschema matchesparse-request.tsnpm run chat/npm run agentstill workSuccess
A reader finishes the TypeScript build, runs
npm run grok:assistantthe same evening, openssrc/assistant.tsbesidesrc/grok-assistant.ts, and can explain the mapping table — including the two ideas that were not just renaming.Notes
mainis protected. PRs required.docs/typescript.mdis the model for structure, voice, and fence markers.