Grok Build CLI direct-worker profile (B-OG-41, #2906)
This is the lightweight CLI profile for dispatching grok (Grok Build CLI) as a direct worker.
Grok Build CLI direct-worker profile (B-OG-41, #2906)
This is the lightweight CLI profile for dispatching `grok` (Grok Build CLI) as a direct worker.
The exact `--help` and bundled-documentation extracts this profile's safety claims (permission-mode semantics, sandbox-profile semantics, the reasoning-effort value set, and the `json`-vs-`streaming-json` event shape distinction) rely on are preserved verbatim, with the command used and binary version, in `grok-build-cli-evidence.md`.
The bundled `direct-shell` invocation profile in `agentic/code/providers/model-capabilities.v1.json` is version-bound to `grok 1.0.50 (c58f321264ba, stable)` and help-evidenced only (`H:` evidence grade, captured from `grok --help` on host `noodle`, 2026-10-10; `.aiwg/working/s1-scratch/cli-help/grok-1.0.50.txt`, plus the installed binary's own bundled docs read with `strings` — never a live prompted run). It stays `unqualified` for dispatch until a human qualifier reviews a live run. The adapter (`tools/ralph-external/lib/grok-build-adapter.mjs`) renders:
grok --prompt-file <briefPath> -m <model> --reasoning-effort <effort> \
--permission-mode dontAsk --sandbox <profile> --cwd <workspace> \
--output-format streaming-json --no-subagents --no-plan \
--no-alt-screen --disable-web-search \
[--allow <rule> ...] [--deny <rule> ...]
The brief rides on `--prompt-file`, never argv text or stdin. `-p/--single <PROMPT>` and `--prompt-json <JSON>` only accept inline argv text/JSON; `--prompt-file <PATH>` ("Single-turn prompt from a file") is a standalone single-turn trigger, so this profile uses it alone and never emits `-p`.
`--permission-mode dontAsk` is the one non-prompting, non-bypass mode. Root `--help` lists `[possible values: default, acceptEdits, auto, dontAsk, bypassPermissions, plan]`. The installed binary's own bundled permission-modes documentation (read via `strings`, not invented) describes each: `default` prompts interactively (would hang headless); `acceptEdits` only auto-approves file edits, other tool calls may still prompt; `plan` is a planning mode (its exact execution behavior is unverified); `bypassPermissions` ("always-approve") is the blanket bypass this profile must never select (same family as `--always-approve`, also never emitted). `dontAsk` ("Only pre-approved tools and built-in read-only handling" / "Strict CI allowlists" / "denies whatever it would have to prompt for") is the only mode that is both non-interactive and non-bypass, so it is fixed and never parameterized. Because `dontAsk` denies anything not pre-approved, a write-capable run needs explicit `--allow`/`--deny` rules (e.g. `Edit`, `Bash(git )`) from the resolved permission tuple; a rule starting with `-`, containing a control character, or equal to a banned blanket-bypass spelling (including a bare ``) is refused before argv is built.
An explicit sandbox profile is emitted for every mode, including read-only. The CLI's own bundled sandbox documentation names the built-in profiles as `off, workspace, devbox, read-only, strict` (or a custom profile from `sandbox.toml`), and states the default with no flag at all is `off` (no sandbox). This profile never relies on that default: `read-only` maps to the CLI's own `read-only` profile and `workspace-write` maps to the CLI's `workspace` profile ("the recommended profile for everyday development"). `devbox` (near-unrestricted write) and `off` are never emitted. `capabilities.sandboxModes` is `["read-only", "workspace-write"]`.
No `--no-ask-user`-equivalent flag is in this version's printed `--help` for the root command. An internal `no-ask-user`/`NO_ASK_USER` argument name exists in the compiled binary's own strings, but it is not in the captured `--help` text, so — per the help-listed-spellings rule — it is never emitted here. Whether an `ask_user_question` tool call blocks indefinitely under `dontAsk` in a genuinely non-interactive run is therefore not evidenced and stays an open risk, not invented away.
The ACP-derived `streaming-json` event grammar is unusually well-evidenced for a compiled binary. `--output-format streaming-json` is "NDJSON: one ACP session update per line, the agent's native format" per `--help`. The installed binary's own bundled output-format documentation (read via `strings`, not invented) gives the full event vocabulary and an example stream:
{"type":"thought","data":"Analyzing the directory structure..."}
{"type":"tool_call","toolCallId":"call_1","title":"Read","kind":"read","status":"in_progress","toolName":"read_file","rawInput":{"path":"src/main.rs"},"content":[],"locations":[]}
{"type":"tool_call_update","toolCallId":"call_1","status":"completed","content":[],"rawOutput":{"lines":42},"locations":[]}
{"type":"text","data":"Here's a summary"}
{"type":"usage","messageId":"resp_1","stopReason":"end_turn","usage":{...},"signature":"..."}
{"type":"end","stopReason":"end_turn","sessionId":"abc123","requestId":"xyz789","usage":{...},"num_turns":7,"modelUsage":{...}}
`end` is documented as always the last event; its `stopReason` is one of `end_turn`, `max_tokens`, `max_turn_requests`, `refusal`, `cancelled`. Both `observeHealthEvent` and the adapter's own `parseDirectFinal` treat only `stopReason === 'end_turn'` as success: a standalone `error` event, or any `tool_call`/`tool_call_update` with `status: 'failed'`, fails the run even if it exits zero or is followed by a clean-looking `end` — a later success never repairs an earlier failure. This grammar is read from the compiled binary's own bundled strings and is never live-observed.
This grammar is distinct from `--output-format json` and from the pre-existing legacy transport. The bundled docs confirm `json` emits ONE object (`text`/`stopReason`/`sessionId`/`requestId`/`usage`/...), not NDJSON lines. Neither `json` nor `streaming-json` matches the `type: "message"` / `type: "result"` shape the separate, pre-existing `src/providers/grok-build-headless.ts` transport assumes — that legacy path, its tests, `tools/providers/grok-build-native-smoke.mjs`, and the generic `grok-build` branch in `tools/ralph-external/lib/worker-health.mjs` are untouched by this profile and keep using their own shape against their own fixtures under `test/fixtures/providers/grok-build/health/`. This direct-shell profile's ACP fixtures live separately under `test/fixtures/providers/grok-build/direct-health/`.
No flag disables a user/project-configured MCP server. Grok has no built-in MCP server to disable (unlike Copilot's bundled `github-mcp-server`); `--disable-web-search` removes the one evidenced built-in external-network tool (`web_search`/`web_fetch`) so the AIWG `worker-invoke` bridge remains the one external route this profile authorizes. Whether a project's own `mcp.json` servers load regardless of this profile's flags is unqualified, not invented away.
Manual smoke run
Live qualification is an open S1 blocker. `grok` authenticates via `XAI_API_KEY` (or a cached `grok login` session) — the conductor's smoke run needs a key from the operator, supplied through the environment only, never argv:
MODEL="<exact observed model id>" # e.g. from `grok models`; never 'auto'/'default'
d=$(mktemp -d) && cd "$d"
echo 'Reply with exactly: grok-smoke-ok' > brief.md
XAI_API_KEY="$XAI_API_KEY" grok --prompt-file brief.md -m "$MODEL" \
--reasoning-effort low --permission-mode dontAsk --sandbox read-only \
--cwd "$d" --output-format streaming-json --no-subagents --no-plan \
--no-alt-screen --disable-web-search </dev/null
echo "exit=$?"
`</dev/null` is deliberate: stdin is never used by this profile, so closing it removes any chance of waiting on a terminal that will never answer.
Reading the result
- Exit code: `echo "exit=$?"` must print `exit=0`. Any non-zero exit is
a fail.
- Every stdout line is a JSON object with a `type`, and the last
line is `{"type":"end",...}` with `"stopReason":"end_turn"`. Any malformed line, any `{"type":"error",...}` line anywhere, any `tool_call`/`tool_call_update` line with `"status":"failed"`, or an `end` whose `stopReason` is not `end_turn`, is a fail — even if the process exits zero (the adapter's `parseDirectFinal` already rejects all of these; see `tools/ralph-external/lib/grok-build-adapter.mjs`).
- No hang: the run must exit on its own within a short bound. A
process still running past the expected completion window is a fail.
- Reply content: concatenate every `{"type":"text","data":...}` chunk
and confirm it contains the literal `grok-smoke-ok`.
Record the installed `grok --version` output, the command above, and the pass/fail outcome (exit code + JSONL validity + `end_turn` + reply content) on the tracking issue. This manual run is evidence only; it never marks the registry profile as qualified for automated dispatch.