grok
v0.1.6xAI Grok CLI as an iii worker; grok::run/start/stop/status/sessions::list spawn the grok CLI for headless turns, mirror raw streaming-json events onto grok::events, and stream AgentEvent frames onto agent::events.
- macOS: arm64 · x64
- Linux: arm64 · armv7 · x64
- Windows: arm64 · x64 · x86
exact versions are immutable; binary and bundle artifacts are digest-pinned.
full markdown
/workers/grok.md. paste it into an llm prompt or pipe it through curl from a worker.install
configuration
- defaults:
always_approve: true
cwd:
model:
events_stream: agent::events
grok_executable:
iii_context: true
raw_events_stream: grok::eventsdependencies
readme
grok
The xAI Grok CLI as an iii worker: the Grok agent exposed as functions and streams on the iii bus, nothing else. The worker spawns the same grok binary the user runs in their terminal, with the same XAI_API_KEY, the same filesystem, and the same working directory. grok::run executes one headless turn (grok --single ) and returns the result; the raw Grok events mirror verbatim onto the grok::events stream, and a translated AgentEvent view lands on agent::events, so the iii console and any sibling worker observe a Grok run exactly like a native harness turn.
Install
iii worker add grokRequires the Grok CLI on the host and XAI_API_KEY in the worker environment. See docs.x.ai for CLI installation and authentication.
Skills
Install the grok agent skill for Claude Code, Cursor, and 30+ other agents:
npx skills add iii-hq/workers --skill grokQuickstart
From zero to a Grok turn over the bus:
curl -fsSL https://install.iii.dev/iii/main/install.sh | sh
iii worker add grok
iii # starts the engine + workerThen talk to it like any other function: from iii trigger grok::run, or from any SDK:
import { registerWorker } from 'iii-sdk';
const iii = registerWorker('ws://127.0.0.1:49134', { workerName: 'demo' });
const res = await iii.trigger({
function_id: 'grok::run',
payload: {
prompt: 'Add a /health endpoint to server.ts and run the tests',
cwd: '/path/to/repo',
},
timeout_ms: 600_000,
});
// { session_id, grok_thread_id, result, stop_reason, num_turns }Or straight from the terminal with the iii trigger CLI:
# one full turn (raise the timeout; the default 30s is too short for agent turns)
iii trigger grok::run --timeout-ms 600000 \
--json '{"prompt":"add a /health endpoint and run the tests","cwd":"/path/to/repo"}'
# quick reads use key=value syntax
iii trigger grok::sessions::list
iii trigger grok::status session_id=<session_id>
# background turn + control
iii trigger grok::start --json '{"prompt":"...","cwd":"/path/to/repo"}'
iii trigger grok::stop session_id=<session_id>
# ask the running engine for a function's description and parameter table
iii trigger grok::run --helpA turn from the CLI, and the published request schema served by --help:


Call grok::run again with the returned session_id to continue the same conversation: the worker maps iii session ids to Grok session ids in engine state and resumes automatically (sessions persist in ~/.grok/sessions).
Two ids come back from every run. session_id is the iii session id: the key for grok::status, grok::stop, resume, and the stream group. grok_thread_id is Grok's own session id (what the worker passes to --resume on the next turn) — returned for reference, not a lookup key.
Functions
| Function | Purpose |
|---|---|
grok::run |
Run one turn, wait, return the final result |
grok::start |
Fire-and-forget turn; progress arrives on the streams |
grok::stop |
Interrupt a live run |
grok::status |
Session state, live flag, turn count |
grok::sessions::list |
All sessions this worker has run |
grok::run accepts either a bare prompt string or a messages array ([{ role: 'user', content: [{ type: 'text', text }] }]) — the same input contract as the claude-code worker and run::start_and_wait, so the acp worker drives Grok with --brain-fn grok::run — plus model, cwd, always_approve, and iii_context.
Raw events
Every line Grok emits on its --output-format streaming-json stream is mirrored verbatim onto the grok::events stream, group_id = session_id. Consumers that want the exact Grok wire format read grok::events; consumers that want harness-shaped frames read agent::events. Same turn, two views.
The streaming-json stream (captured from Grok CLI 0.2.77) is delta-based: assistant text arrives as {"type":"text","data":" lines, the turn closes with {"type":"end","stopReason","sessionId","requestId"}, and failures arrive as {"type":"error","message"}. The worker accumulates the text deltas and emits one message_complete frame on agent::events at end.
Note: the Grok CLI streaming-json schema is not formally published, so the typed model in
src/grok/events_types.rsis lenient — unrecognized event types pass through verbatim ongrok::eventsand are skipped on the translated stream rather than failing the turn. Headless output carries no token usage and does not break out tool-call events today.
Configuration
engine_url: ws://127.0.0.1:49134
defaults:
model: "" # empty = Grok CLI default (e.g. grok-build-0.1)
cwd: "" # default working directory for runs
always_approve: true # auto-approve tool/command execution on headless turns
events_stream: agent::events # translated AgentEvent frames
raw_events_stream: grok::events # verbatim Grok streaming-json events
grok_executable: "" # path to the grok CLI; empty = PATH resolutionAuth is the Grok CLI's own: the worker inherits XAI_API_KEY from its environment. always_approve keeps headless turns from blocking on an interactive approval prompt; set it to false to let the CLI's approval policy gate tool execution.
The agent on the bus
By default every turn carries the iii runtime context, prepended to the prompt on the first turn of a session: the same engine-grounded rules as the harness identity prompts, retargeted to the iii CLI the agent reaches through its shell. The agent discovers capabilities from the live engine instead of memory — iii trigger engine::functions::list to find function ids, iii trigger as the contract before every first call, the registry flow (directory::registry::workers::list/info, worker::add) when nothing registered fits — plus the calling rules and error-handling discipline that go with them. Local file edits stay on Grok's native tools; backend actions go through registered functions.
# the agent answers this by querying the live engine itself
iii trigger grok::run --timeout-ms 300000 \
--json '{"prompt":"List every worker connected to this engine and what each one does.","cwd":"/tmp"}'With the context on, Grok reaches for iii trigger engine::workers::list itself and reports the live mesh — every worker and what it does — instead of guessing from ps and config files:

The context is injected once at the start of a session; resumed turns rely on the existing session history. Turn it off per call with "iii_context": false or globally in config.yaml.
Observability
Every grok::run is an ordinary traced invocation on the engine: the trace carries the full input payload and the output (result, stop reason) as span events, with per-function p50/p95/p99 in the console's trace explorer — no extra instrumentation in the worker. Headless Grok output carries no token usage today, so the worker does not report it.
How it maps
| Grok | iii |
|---|---|
one headless grok --single turn |
grok::run invocation |
| every streaming-json line, verbatim | grok::events stream frame |
accumulated text deltas at end |
message_complete frame on agent::events |
turn end |
turn_end + agent_end frames, function return value |
end.sessionId → --resume next turn |
engine state scope grok_sessions, keyed by iii session_id |
| extra capability | another iii worker on the bus (shell, database, storage, ...) |
api reference (json)
{
"functions": [
{
"description": "Internal: reload grok configuration when it changes.",
"metadata": {},
"name": "grok::on-config-change",
"request_schema": {
"properties": {},
"type": "object"
},
"response_schema": {
"properties": {
"ok": {
"type": "boolean"
}
},
"type": "object"
}
},
{
"description": "Run one Grok turn and wait for the result. Accepts `prompt` or a `messages` array; streams raw Grok events onto grok::events, AgentEvent frames onto agent::events, and returns {session_id, result, stop_reason}.",
"metadata": {},
"name": "grok::run",
"request_schema": {
"definitions": {
"Message": {
"properties": {
"content": {
"description": "Either a plain string or an array of content blocks."
},
"role": {
"type": "string"
}
},
"required": [
"content",
"role"
],
"type": "object"
}
},
"properties": {
"always_approve": {
"default": null,
"description": "Auto-approve tool/command execution for this headless turn (--always-approve).",
"type": [
"boolean",
"null"
]
},
"cwd": {
"default": null,
"description": "Working directory the turn runs in (--cwd).",
"type": [
"string",
"null"
]
},
"iii_context": {
"default": null,
"description": "Inject the iii runtime discovery prompt as the leading instructions (default true via config).",
"type": [
"boolean",
"null"
]
},
"messages": {
"description": "Alternative to prompt: role/content messages; the last user entry becomes the prompt.",
"items": {
"$ref": "#/definitions/Message"
},
"type": [
"array",
"null"
]
},
"model": {
"default": null,
"description": "Model id; empty = Grok default (e.g. grok-build-0.1).",
"type": [
"string",
"null"
]
},
"prompt": {
"default": null,
"description": "The user prompt for this turn.",
"type": [
"string",
"null"
]
},
"session_id": {
"default": null,
"description": "iii session id; reuse to resume the same Grok thread.",
"type": [
"string",
"null"
]
},
"timeout_ms": {
"default": null,
"description": "Reserved for callers; not forwarded.",
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
}
},
"title": "RunRequest",
"type": "object"
},
"response_schema": {
"properties": {
"busy": {
"type": "boolean"
},
"grok_thread_id": {
"type": [
"string",
"null"
]
},
"is_error": {
"type": "boolean"
},
"num_turns": {
"type": "integer"
},
"reason": {
"type": "string"
},
"result": {
"type": "string"
},
"session_id": {
"type": "string"
},
"stop_reason": {
"type": "string"
}
},
"type": "object"
}
},
{
"description": "List every Grok session this worker has run.",
"metadata": {},
"name": "grok::sessions::list",
"request_schema": {
"properties": {},
"type": "object"
},
"response_schema": {
"properties": {
"sessions": {
"items": {
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
},
{
"description": "Start a Grok turn and return immediately; watch grok::events / agent::events (group_id = session_id) for progress and turn_end.",
"metadata": {},
"name": "grok::start",
"request_schema": {
"definitions": {
"Message": {
"properties": {
"content": {
"description": "Either a plain string or an array of content blocks."
},
"role": {
"type": "string"
}
},
"required": [
"content",
"role"
],
"type": "object"
}
},
"properties": {
"always_approve": {
"default": null,
"description": "Auto-approve tool/command execution for this headless turn (--always-approve).",
"type": [
"boolean",
"null"
]
},
"cwd": {
"default": null,
"description": "Working directory the turn runs in (--cwd).",
"type": [
"string",
"null"
]
},
"iii_context": {
"default": null,
"description": "Inject the iii runtime discovery prompt as the leading instructions (default true via config).",
"type": [
"boolean",
"null"
]
},
"messages": {
"description": "Alternative to prompt: role/content messages; the last user entry becomes the prompt.",
"items": {
"$ref": "#/definitions/Message"
},
"type": [
"array",
"null"
]
},
"model": {
"default": null,
"description": "Model id; empty = Grok default (e.g. grok-build-0.1).",
"type": [
"string",
"null"
]
},
"prompt": {
"default": null,
"description": "The user prompt for this turn.",
"type": [
"string",
"null"
]
},
"session_id": {
"default": null,
"description": "iii session id; reuse to resume the same Grok thread.",
"type": [
"string",
"null"
]
},
"timeout_ms": {
"default": null,
"description": "Reserved for callers; not forwarded.",
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
}
},
"title": "RunRequest",
"type": "object"
},
"response_schema": {
"properties": {
"session_id": {
"type": "string"
},
"started": {
"type": "boolean"
}
},
"type": "object"
}
},
{
"description": "Point-in-time status of a Grok session.",
"metadata": {},
"name": "grok::status",
"request_schema": {
"properties": {
"session_id": {
"description": "iii session id returned by grok::run / grok::start.",
"type": "string"
}
},
"required": [
"session_id"
],
"title": "SessionIdRequest",
"type": "object"
},
"response_schema": {
"properties": {
"live": {
"type": "boolean"
},
"record": {
"type": [
"object",
"null"
]
},
"session_id": {
"type": "string"
}
},
"type": "object"
}
},
{
"description": "Interrupt a live Grok run for a session.",
"metadata": {},
"name": "grok::stop",
"request_schema": {
"properties": {
"session_id": {
"description": "iii session id returned by grok::run / grok::start.",
"type": "string"
}
},
"required": [
"session_id"
],
"title": "SessionIdRequest",
"type": "object"
},
"response_schema": {
"properties": {
"reason": {
"type": [
"string",
"null"
]
},
"session_id": {
"type": "string"
},
"stopped": {
"type": "boolean"
}
},
"type": "object"
}
}
],
"triggers": []
}