> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Grok Bot

> Send monitor-only Grok Bot sessions to Runlayer with scoped team or plugin hooks.

Grok Bot runs work on a cloud computer. The macOS desktop app and iOS app are
control surfaces, so they do not inherit AI Watch binaries, MDM settings, or the
local Grok CLI hooks under `~/.grok`.

Use Grok Bot's Cursor-format team or plugin hooks to send session events to
Runlayer. This integration is **monitor-only**: the collector cannot block
tools, enforce MCP policy, read Runlayer data, or administer your workspace.

## 1. Create a scoped key

In Runlayer, go to **Settings → Organization API Keys**, create a key with only
the **Grok Bot Hooks** permission, and copy it when shown. Runlayer stores only
its hash.

Add the value to the Grok Bot team or cloud-computer secret store as
`RUNLAYER_GROK_BOT_HOOK_KEY`. Do not commit it to a repository, and do not use
an AI Watch organization key or Agent Account secret.

## 2. Add the hook helper

Create `.grokbot/runlayer_hook.py` in each repository used by Grok Bot. Replace
the hostname in `COLLECTOR_URL` with your Runlayer tenant hostname. Keep the full
HTTPS URL literal so repository content cannot redirect the credential.

```python theme={null}
#!/usr/bin/env python3
import os
import sys
import time
import urllib.error
import urllib.request
import uuid

COLLECTOR_URL = "https://YOUR-TENANT.runlayer.com/api/v1/hooks/grok-bot/events"
MAX_PAYLOAD_BYTES = 256 * 1024
ALLOW = '{"permission":"allow"}'


class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


def deliver(payload: bytes, key: str) -> None:
    delivery_id = str(uuid.uuid4())
    request = urllib.request.Request(
        COLLECTOR_URL,
        data=payload,
        method="POST",
        headers={
            "Content-Type": "application/json",
            "X-Runlayer-Delivery-ID": delivery_id,
            "x-runlayer-api-key": key,
        },
    )
    opener = urllib.request.build_opener(NoRedirect)
    for attempt, delay in enumerate((0, 0.2, 0.8)):
        if delay:
            time.sleep(delay)
        try:
            with opener.open(request, timeout=2) as response:
                if 200 <= response.status < 300:
                    return
        except urllib.error.HTTPError as error:
            if error.code < 500 and error.code != 429:
                return
            if attempt == 2:
                return
        except (urllib.error.URLError, TimeoutError):
            if attempt == 2:
                return


def main() -> int:
    try:
        payload = sys.stdin.buffer.read(MAX_PAYLOAD_BYTES + 1)
        key = os.environ.get("RUNLAYER_GROK_BOT_HOOK_KEY", "")
        if key and len(payload) <= MAX_PAYLOAD_BYTES:
            deliver(payload, key)
    except Exception:
        pass
    sys.stdout.write(ALLOW)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
```

The helper reuses one delivery ID across transient retries, never logs the key
or payload, and always returns Grok Bot's explicit allow response. A Runlayer
outage therefore cannot block or change a Bot run.

## 3. Register supported team hooks

In Grok Bot, add command hooks for the events below at team scope. Teams that
manage Cursor-format plugins as code can use this as `hooks/hooks.json` in the
plugin and assign the plugin to Grok Bot users.

Runlayer supports exactly the configured events below. This list defines the
integration contract; it is not a claim that every Grok Bot version emits every
event during every run. Other event names are rejected by the collector.

```json theme={null}
{
  "version": 1,
  "hooks": {
    "sessionStart": [{ "command": "python3 .grokbot/runlayer_hook.py", "timeout": 3, "failClosed": false }],
    "sessionEnd": [{ "command": "python3 .grokbot/runlayer_hook.py", "timeout": 3, "failClosed": false }],
    "beforeSubmitPrompt": [{ "command": "python3 .grokbot/runlayer_hook.py", "timeout": 3, "failClosed": false }],
    "afterAgentThought": [{ "command": "python3 .grokbot/runlayer_hook.py", "timeout": 3, "failClosed": false }],
    "preToolUse": [{ "command": "python3 .grokbot/runlayer_hook.py", "timeout": 3, "failClosed": false }],
    "postToolUse": [{ "command": "python3 .grokbot/runlayer_hook.py", "timeout": 3, "failClosed": false }],
    "postToolUseFailure": [{ "command": "python3 .grokbot/runlayer_hook.py", "timeout": 3, "failClosed": false }],
    "subagentStart": [{ "command": "python3 .grokbot/runlayer_hook.py", "timeout": 3, "failClosed": false }],
    "subagentStop": [{ "command": "python3 .grokbot/runlayer_hook.py", "timeout": 3, "failClosed": false }],
    "afterAgentResponse": [{ "command": "python3 .grokbot/runlayer_hook.py", "timeout": 3, "failClosed": false }],
    "stop": [{ "command": "python3 .grokbot/runlayer_hook.py", "timeout": 3, "failClosed": false }],
    "preCompact": [{ "command": "python3 .grokbot/runlayer_hook.py", "timeout": 3, "failClosed": false }]
  }
}
```

Start a new Bot run, then open **Sessions** in Runlayer and filter for **Grok
Bot**. If events do not appear, confirm **Grok Bot** is enabled under **Settings
→ Agent session monitoring**, the secret is available on the cloud computer,
and the collector URL uses the correct tenant hostname.
