# grok

> xAI 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.

| field | value |
|-------|-------|
| version | 0.1.1 |
| type | binary |
| repo | https://github.com/iii-hq/workers |
| supported_targets | x86_64-apple-darwin, aarch64-apple-darwin, i686-pc-windows-msvc, x86_64-pc-windows-msvc, aarch64-pc-windows-msvc, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, armv7-unknown-linux-gnueabihf |
| author | iii |

## installation

```sh
iii worker add grok@0.1.1
```

## configuration

```yaml
- defaults:
    always_approve: true
    cwd: 
    model: 
  events_stream: agent::events
  grok_executable: 
  iii_context: true
  raw_events_stream: grok::events
```

## readme

# grok

The [xAI Grok CLI](https://docs.x.ai) 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 <prompt> --output-format streaming-json`) 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

```bash
iii worker add grok
```

Requires the Grok CLI on the host and `XAI_API_KEY` in the worker environment. See [docs.x.ai](https://docs.x.ai) for CLI installation and authentication.

## Skills

Install the `grok` agent skill for Claude Code, Cursor, and 30+ other agents:

```bash
npx skills add iii-hq/workers --skill grok
```

## Quickstart

From zero to a Grok turn over the bus:

```bash
curl -fsSL https://install.iii.dev/iii/main/install.sh | sh
iii worker add grok
iii   # starts the engine + worker
```

Then talk to it like any other function: from `iii trigger grok::run`, or from any SDK:

```ts
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:

```bash
# 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 --help
```

A turn from the CLI, and the published request schema served by `--help`:

![iii trigger grok::run returning the result over the bus](https://raw.githubusercontent.com/iii-hq/workers/main/grok/assets/cli-run.png)

![iii trigger grok::run --help printing the typed request schema as a parameter table](https://raw.githubusercontent.com/iii-hq/workers/main/grok/assets/cli-help.png)

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":"<chunk>"}` 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.rs`](src/grok/events_types.rs) is lenient — unrecognized event types pass through verbatim on `grok::events` and 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

```yaml
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 resolution
```

Auth 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 <fn> --help` 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.

```bash
# 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:

![Grok answering a live-system question by querying the engine through the iii CLI](https://raw.githubusercontent.com/iii-hq/workers/main/grok/assets/iii-context.png)

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": []
}
```
