Run and recover
On this page
The agent connects only to loopback TCP: 127.0.0.1 or ::1. The default port is 8765. Run the agent and gateway on the same host.
Local simulation needs no public hosting. You operate the gateway and agent on this host. Grok/xAI hosts Grok Bot; it does not host these processes for you.
The gateway has local stdio MCP and authenticated HTTP MCP through
grok-gadgets-gateway serve. serve keeps running without an MCP client. Both modes
can run the loopback device listener. Never tunnel the device port.
A tunnel adds reachability. It does not add authentication.
HARD-GROK-REMOTE-001 tracks the remote route.
See the hosting FAQ
for the future cloud route and product hosting choices.
Start the agent
For development, grok-linux-agent dev ./my_gadget.py is all you need: it starts a gateway in
the same process, trusts your gadget on loopback and prints MCP client settings. Add --stdio
when an MCP client should start it; that path needs no token at all.
To run beside a long-running gateway service instead:
- Install the gateway and run
grok-gadgets-gateway initonce. - Issue a device token into a private file:
grok-gadgets-gateway enroll desk-lamp --token-file desk-lamp.token. The device ID must match yourGadget(...). - Start the gateway:
grok-gadgets-gateway serve. - Run
grok-linux-agent --factory-file ./my_gadget.py --token-file desk-lamp.token.GROK_GADGETS_DEVICE_TOKENalso works;GROK_DEVICE_TOKENis deprecated. - Request device capabilities through gateway MCP.
The default agent is a software lamp. Add --simulate-button to queue simulated press and release events. Use --factory-file ./my_gadget.py (one module-level Gadget, or path.py:function) or --factory module:function for a trusted application. Use --port for a different loopback port.
Connect a local MCP client
For the running serve process in the quickstart, use a client that supports
Streamable HTTP and an Authorization header:
{
"mcpServers": {
"grok-gadgets": {
"url": "http://127.0.0.1:8766/mcp",
"headers": {"Authorization": "Bearer <mcp-token>"}
}
}
}
Replace <mcp-token> privately with the single line from
~/.config/grok-gadgets/mcp-token. If you set XDG_CONFIG_HOME, use that directory
instead of ~/.config. This is the MCP token, not the device token. Keep the settings
private. Exact client settings depend on the client; Grok Bot cannot open this URL.
Alternatively, stop serve and let a local MCP client start the gateway over stdio.
Do not start both modes on the same device port. Use absolute paths. The listener on
--device-port runs while the client keeps the gateway running:
{
"mcpServers": {
"grok-gadgets": {
"command": "/home/pi/grok-gadget/.venv/bin/grok-gadgets-gateway",
"args": [
"--credentials", "/home/pi/.config/grok-gadgets/credentials.json",
"--device-port", "8765"
]
}
}
}
The credential file must have mode 600. A cloud Grok Bot cannot start this command on
your computer. This snippet is not verified with a specific MCP client.
SDK integration tests start DeviceServer directly on temporary loopback ports. They do not use a paid API call or a live Grok account.
Keep both services running while you need the device. Keep ordinary peripheral controls
independent of Grok Bot. When the agent stops, the gateway marks it offline. Closing an
HTTP client leaves serve running. A stdio gateway ends when its client closes stdin.
A command can be accepted without a confirmed result. A reported result does not prove a physical effect. Record each evidence level separately.
Connection limits
| Setting | Default |
|---|---|
| Poll interval | 100 ms |
| Socket timeout | 2 seconds |
| Handler timeout | 5 seconds |
| Failed connection or session attempts | 8 (--max-attempts N, 1–32); unlimited with --retry-forever |
| Reconnect delay | Ceiling starts at 250 ms and doubles to 5 seconds (30 seconds with --retry-forever); each delay is random between half the ceiling and the ceiling |
| Healthy session needed to reset the failure budget | 10 seconds |
Revocation and contract errors stop the agent immediately. unauthorized before the
first successful hello stops it immediately. After a successful hello in the same run,
one unauthorized is retried with backoff, because a gateway can report a transient
credential-file read failure that way; a second consecutive unauthorized stops the
agent. unavailable, busy and connection failures are retried. Each request rechecks
gateway revocation. Shutdown interrupts a reconnect delay.
A lost event acknowledgement retains the event ID for reconnect. The gateway does not replay a dispatched command into a new session. A handler timeout sends a failed acknowledgement (handler_timeout) and keeps the session. If an acknowledgement reaches the gateway after the command closed, the gateway answers late_ack; the agent drops that acknowledgement and keeps polling on the same session. On disconnect, the gateway marks a dispatched command unconfirmed.
Async handlers must respond to cancellation. A plain-function handler runs in a worker thread: the timeout stops the wait, not the thread. Do not detach physical actions from a handler.
Stop and exit codes
SIGTERM (systemctl stop) and Ctrl-C stop the agent the same way: it cancels the running
handler, so its finally blocks run, then it calls Device.on_shutdown callbacks, then
it exits with code 0. A plain-function handler that is still running delays exit until it
returns.
Every other stop prints Agent stopped (<code>). <hint>. The code is a fixed string; it
never contains tokens, arguments or gateway text.
| Exit | Codes | Meaning |
|---|---|---|
| 0 | signal |
Stopped by SIGTERM or SIGINT |
| 1 | internal_error |
Unexpected failure; inspect it privately |
| 2 | factory_error, token_missing, token_invalid, invalid_option, simulation_only |
Configuration; argparse usage errors also exit 2 |
| 3 | unauthorized, revoked |
Check the device ID and token; see token recovery below |
| 4 | protocol_mismatch, invalid_request, invalid_response, frame_too_large, duplicate_conflict |
Protocol contract; for example, a hello larger than 2048 bytes |
| 5 | reconnect_exhausted |
No gateway answered within the attempt budget |
State and restart behavior
Each poll reports stored state. This refreshes gateway receipt time; it does not prove a new measurement. Publish new observations when necessary.
The boot ID stays the same during reconnects within one process. Restart creates a new boot ID. The event queue and command cache exist only in memory. Gateway restart removes its history and changes its cursor epoch.
After a gateway restart, the gateway can send a command ID that the device already
executed. The same arguments return the cached status with current state. Changed
arguments return a failed acknowledgement with duplicate_conflict; the agent stays
connected.
Recover a device token
Issue a new token for the same device ID with
grok-gadgets-gateway enroll <device-id> --rotate --token-file <file>. The old token stops
working at once. An agent started with --token-file <file> reads the file again on its next
connection, so it reconnects without a restart. Rotating also reactivates a revoked ID.
XDG_CONFIG_HOME changes where the gateway keeps its registry.
Prepare a Linux user service
The template is examples/grok-gadget.service. It is not yet verified under real systemd.
A test runs its ExecStart line from a temporary home folder without systemd.
The template expects:
~/grok-gadget/.venvwith this SDK installed;~/grok-gadget/my_gadget.pywith acreate()function;~/.config/grok-gadgets/linux-agent.envwithGROK_GADGETS_DEVICE_TOKEN=..., mode600.
Steps:
- Copy the template to
~/.config/systemd/user/grok-gadget.service. Change paths if necessary. - Run
chmod 600 ~/.config/grok-gadgets/linux-agent.env. - Run
systemctl --user daemon-reload. - Run
systemctl --user start grok-gadget, then checkjournalctl --user -u grok-gadget. - To start at boot without a login session, run
loginctl enable-linger "$USER". - Run
systemctl --user enable grok-gadgetafter manual operation works.
The template uses --retry-forever, so the agent waits for a gateway that starts later.
Restart=on-failure with RestartSec=5 restarts it after other failures.
RestartPreventExitStatus=2 3 4 keeps it stopped after configuration, authorization
and contract errors; fix the cause, then start it again. A user unit cannot order itself
after a system network target, so the template has no After=network.target.
Remove the test setup
- Stop the service or agent.
- Revoke its gateway device token.
- If no longer needed, remove the virtual environment.
Keep unrelated gateway devices intact. Do not put credentials, arguments, private state or event contents in issue logs.
Run gateway integration tests
Use the reviewed gateway source checkout:
GROK_GATEWAY_SOURCE=../grok-gadgets-gateway/src uv run python -m unittest discover -s tests -v
Without this variable, seven integration tests skip. Unit tests still run independently. Each integration test has a time limit, so a hang is reported as a failure. Component CI runs unit checks. Cross-repository checks must supply the pinned gateway source.