@agent-ops/grok-nats-channel
v0.8.0
Published
Synadia Agent Protocol for NATS channel for Grok. Exposes a Grok session as a discoverable, spec-compliant agent on NATS via an MCP server.
Readme
NATS Channel for Grok
Connect Grok to NATS messaging as a spec-compliant Synadia Agent Protocol for NATS agent.
The MCP server registers an agents micro service, exposes a
prompt endpoint at agents.prompt.<machine>.<project>.<session>, a status
endpoint at agents.status.<machine>.<project>.<session> (replies with the same
payload as a heartbeat), publishes heartbeats at
agents.hb.<machine>.<project>.<session> (the verb is the abbreviation hb),
and bridges prompt requests into the Grok session.
Harness identity (grok) lives in the service metadata (agent key),
not in the subject.
Replies stream back as typed JSON chunks
({"type":"response","data":"..."}) terminated by an empty headerless
message - the protocol's uniform end-of-stream signal.
This is the Grok counterpart to the NATS Channel for Claude Code adapter in the Synadia Agents reference implementation.
Grok + sesh NATS
Use this adapter to make your Grok sessions first-class, discoverable agents (gr.<owner>.<name>) on any NATS mesh — including for sesh and other Synadia orchestration tools.
Typical flow with sesh:
- Place the adapter at
~/projects/grok-nats-channel(or your path). - In a Grok TUI session: run
/grok-nats-channel:launcher enable - Then
/grok-nats-channel:configure(pick NATS context or use demo; optionally set session name andpermissions queryfor remote callers). - Restart Grok — the MCP server starts and registers on NATS.
- In sesh (or
nats micro ls): yourgragent appears. Route tasks to it; prompts arrive in Grok as:
Reply with the built-in<channel source="nats" request_id="..." session="..." ts="..."> ... </channel>reply(request_id, text, done)tool. - (Future) When published under the
@agent-opsnpm scope as@agent-ops/grok-nats-channel, enable viabunxor Grok marketplace without a local clone.
See the Quick Setup below, the launcher + configure skills, and "Enabling in Grok" section for details. Heartbeats, discovery, attachments, and query-permission chunks are all supported per the v0.3 protocol.
Prerequisites
- Bun - the MCP server runs on Bun. Install with
curl -fsSL https://bun.sh/install | bash, and make surebunis on yourPATH. - NATS CLI - for managing contexts and testing.
- A NATS server to connect to (local or remote) - the plugin defaults to
demo.nats.io.
Quick Setup
1. Activate the grok-nats-channel in your Grok session.
The adapter is provided as a channel plugin for Grok. Enable it through
your Grok environment's marketplace or channel launcher for the Synadia
NATS Agent Protocol (the server entrypoint is server.ts / bun server.ts).
By default, the server connects to demo.nats.io (no credentials required)
and registers a micro service on agents.prompt.<machine>.<project>.<session>,
where <session> defaults to the working directory name.
2. (Optional) Configure the channel.
The /grok-nats-channel:configure skill manages connection, session naming,
and permissions. All state lives in ~/.grok/channels/nats/config.json.
| Command | Description |
| --- | --- |
| /grok-nats-channel:configure | Show current config, list available contexts, and offer to switch |
| /grok-nats-channel:configure list | List available NATS CLI contexts |
| /grok-nats-channel:configure <context-name> | Select a NATS CLI context to connect to |
| /grok-nats-channel:configure session <name> | Override the session name (the <session> token in agents.prompt.<machine>.<project>.<session>) |
| /grok-nats-channel:configure session clear | Remove session name override, revert to CWD basename |
| /grok-nats-channel:configure permissions terminal | Prompt for permissions in the terminal (default) |
| /grok-nats-channel:configure permissions query | Relay permission prompts as protocol query chunks |
| /grok-nats-channel:configure permissions clear | Reset permissions to default |
| /grok-nats-channel:configure clear | Remove all configuration |
To connect to your own NATS server, use a NATS CLI context. List your contexts
with nats context ls, then:
/grok-nats-channel:configure <context-name>This writes the selected context to ~/.grok/channels/nats/config.json.
The server reads connection details (URL, credentials) from
~/.config/nats/context/<name>.json.
3. How NATS prompts appear to Grok (the <channel> notification)
When a prompt arrives over NATS, Grok receives it wrapped in a host-specific channel notification:
<channel source="nats" request_id="..." session="..." ts="...">
[optional attachments header + file paths]
The user's prompt text here...
</channel>source="nats"identifies the origin.request_idis the opaque ID you must pass to thereplytool.sessionis the resolved 5th subject token (<name>).- Attachments (if any) are staged to absolute paths on disk and listed at the top of the block so the model can
Readthem.
4. Reply using the reply tool
To send a response back to the NATS caller, use the built-in reply tool:
- Pass the
request_idfrom the<channel>block. text: the content to deliver (as a{"type":"response","data":...}chunk).done: setfalsefor intermediate/streaming replies;true(default) emits the empty-body terminator that signals completion per the protocol.
Example (conceptual):
reply(request_id="abc123", text="Working on it...", done=false)
reply(request_id="abc123", text="Here is the final answer.", done=true)The server handles chunking for large payloads, status acks, and cleanup.
5. Send a prompt (from a client).
With the @synadia-ai/agents TypeScript SDK:
import { connect } from "@nats-io/transport-node";
import { Agents } from "@synadia-ai/agents";
const nc = await connect({ servers: "nats://localhost:4222" });
const agents = new Agents({ nc });
const [agent] = await agents.discover();
for await (const msg of await agent!.prompt("hello Grok")) {
if (msg.type === "response") process.stdout.write(msg.text);
}
await agents.close();
await nc.close();Or directly via the NATS CLI (plain-text shorthand per spec §5.1):
nats req agents.prompt.<machine>.<project>.<session> "Hello Grok" \
--replies=0 --reply-timeout=30s --timeout=90sGrok's response streams back as typed JSON chunks on the reply subject; an empty headerless message signals completion.
Protocol compliance
This adapter implements the Synadia Agent Protocol for NATS v0.3 end-to-end,
and additively advertises the in-development sesh v0.4 capability via
$SRV.INFO metadata so the sesh shim can route v0.4 traffic (v2 envelopes,
AgentCard L3 lookup) to this agent without disturbing the v0.3 wire.
v0.4 surface (slice 6 of the v0.4 rollout — see
docs/plans/2026-05-24-v0.4-sdk-and-adapters.md):
- Service metadata adds
sesh.protocol_version: "0.4"andsesh.v04_capabilities: "messages,artifacts,cards". The v0.3protocol_version: "0.3"key stays — third-party SDK probes continue to see a v0.3-conformant agent. - Prompt endpoint at
agents.prompt.<machine>.<project>.<session>(queue groupagents-prompt). Decodes the Message envelope and bridges into the MCP notification pipeline; replies stream back asagents.task.stream.*events on the consumer side. - AgentCard L3 registration at
agents.card.<machine>.<session>.<name>— returns a partial card with one representative skill (grok.code). The shim signs and serves the merged L1+L2+L3 card over HTTPS.
Streaming Artifacts / Messages KV writes are deferred to a follow-up — the v2
handler currently bridges into the same MCP pipeline as v0.3 and replies on
msg.reply with v0.3-format chunks. The shim re-projects those chunks into v2
agents.task.stream.* events on the consumer side.
The v0.3 wire below is unchanged.
- Registers as an
agentsNATS micro service (§3.1 - the bare subject-safe token). - Service metadata includes
agent,owner,session, andprotocol_version: "0.3"(§3.2). promptendpoint declares the server-negotiatedmax_payload(read fromnc.info.max_payloadat startup and formatted into the §2.1\d+(B|KB|MB|GB)grammar —1MBagainst a defaultnats-server, larger if the operator bumped--max_payload),attachments_ok: "true"(§2.1), and queue group"agents"(§3.3).- Accepts both plain-text shorthand and JSON envelopes with optional base64-encoded attachments (§5.1, §5.2, §5.3). Inbound attachments are staged to a per-request temp directory and exposed to Grok via file paths.
- Rejects malformed envelopes, empty payloads, oversize requests, and
invalid base64 with
Nats-Service-Error-Code: 400(§9). - Emits typed response chunks
{"type":"response","data":"..."}(§6.3) terminated by an empty headerless message (§6.5). Large responses are split into multiple UTF-8-safe chunks that each fit undermax_payload. - Publishes periodic
{"type":"status","data":"ack"}keep-alives (§6.4) every 30 s while a request is open, resetting the caller's 60-second inactivity timeout. - Publishes heartbeats at
agents.hb.<machine>.<project>.<session>every 5 s with the full §8.3 payload includinginstance_id(§8). - Relays Grok permission prompts as mid-stream
querychunks (§7) whenpermissions.mode = query.
The caller-side SDK at the Synadia Agents repository is the canonical counterpart.
Session names
The micro service prompt subject is agents.prompt.<machine>.<project>.<session> (clean v0.4 scheme; identity lives in metadata, not the subject). Heartbeats go to agents.hb.<machine>.<project>.<session> and the status endpoint replies on agents.status.<machine>.<project>.<session>.
- Default: sanitized basename of the working directory (e.g.,
my-project) - Canonical override: set
SESH_SESSIONenv var — the sesh contract; populated automatically bysesh up --execandorch-spawnSESH_* exports. Also resolved via the.sesh/sessions/<label>.jsonstate-walk when unset. - Legacy override: set
NATS_SESSION_NAMEenv var (back-compat), or use/grok-nats-channel:configure session <name> - Multiple sessions: if the default name is already taken by another
grok instance owned by the same user, the adapter auto-appends
-2,-3, etc.
Discover running sessions via the protocol's discovery subjects:
nats req '$SRV.INFO.agents' '' --replies=0 --timeout=2s
nats req '$SRV.PING.agents' '' --replies=0 --timeout=2sOr via the NATS Micro CLI:
nats micro ls
nats micro info agentsTools exposed to the assistant
| Tool | Purpose |
| --- | --- |
| reply | Send a response over NATS. Takes request_id + text. The server wraps the text in a {"type":"response","data":...} chunk. Set done=false for intermediate replies; done=true (default) emits the empty-body terminator. |
Permissions
When Grok needs permission to run a tool, the adapter can either
prompt in the terminal (default) or relay the request as a protocol
query chunk on the active NATS stream. This is controlled by the
permissions config.
Terminal mode (default)
Permission prompts appear directly in the Grok terminal / TUI session. No extra configuration needed.
Query mode
Permission requests are emitted as {"type":"query","data":{...}}
chunks on the active stream's reply subject (spec §7). The caller
replies on the query's dynamic _INBOX with yes/no, and the adapter
forwards the decision back to the harness.
/grok-nats-channel:configure permissions queryTo switch back to terminal mode:
/grok-nats-channel:configure permissions terminalThe legacy value "nats" is still accepted as an alias for "query" so
old configs keep working. The older permissions.subject override field
has been removed - query chunks always use a fresh NATS inbox per
request.
If Grok asks for permission while no NATS request is active (for
example from direct terminal input), the adapter denies by default in
query mode; use permissions terminal instead if you want interactive
approval in that case.
Handling permission queries with the SDK
for await (const msg of await remote.prompt("rm -rf /tmp/stale")) {
if (msg.type === "query") {
await msg.reply("yes"); // or "no"
}
if (msg.type === "response") {
process.stdout.write(msg.text);
}
}Or with the NATS CLI, by publishing to the reply_subject from the
query chunk:
nats pub _INBOX.Xj7k9Q2pA "yes"If no reply is received within 2 minutes, the permission defaults to deny.
Access control
NATS server authentication and authorization handle access control. If a
user can connect and publish to agents.prompt.<machine>.<project>.<session>, they can
interact with Grok. No additional pairing or allowlist is needed.
Grok authentication
Grok sessions and model access are authenticated through the host Grok application and xAI infrastructure. The NATS channel adapter requires no separate API keys or credentials — it communicates with the local Grok harness exclusively via the MCP protocol.
Configuration
State lives in ~/.grok/channels/nats/:
| File | Purpose |
| --- | --- |
| config.json | Selected NATS context, session name override, and permission settings |
| attachments/<request_id>/ | Per-request staged attachments; auto-cleaned on reply completion |
NATS CLI contexts live in ~/.config/nats/context/<name>.json.
config.json
{
"context": "my-context",
"sessionName": "my-session",
"permissions": {
"mode": "query"
}
}| Field | Default | Description |
| --- | --- | --- |
| context | (none - uses demo.nats.io) | NATS CLI context name |
| sessionName | CWD basename | Override the session name |
| permissions.mode | terminal | terminal or query (nats accepted as legacy alias for query) |
Environment variables
| Variable | Overrides | Default |
| --- | --- | --- |
| SESH_SESSION | Session name (the <session> token in agents.prompt.<machine>.<project>.<session>); canonical sesh contract — wins over NATS_SESSION_NAME. Also resolved via .sesh/sessions/<label>.json state-walk when unset. | (unset) |
| NATS_SESSION_NAME | Legacy operator override for session name. | sanitized basename of $GROK_CWD or CWD |
| NATS_STATE_DIR | State directory location | ~/.grok/channels/nats |
| NATS_CONTEXT | NATS CLI context name (higher precedence than config file) | (none) |
| NATS_URL | Raw NATS URL (fallback when no context) | (none - falls back to demo.nats.io) |
| SESH_ROLE | Free-form role token (^[a-z0-9_-]+$, 1–63 chars). Identifies the function this agent plays in the swarm — e.g. implementer, verifier, spy. Surfaced as metadata.role on the NATS Micro service and as role in sesh's session manifest. | worker |
| SESH_CLASS | One of active or observer. Coordination-subject routing keys on this: active agents subscribe to workers.*, observer agents subscribe to spies.*. | active |
Enabling in Grok (Grok-side Launcher)
The adapter is distributed as a Grok plugin (skills + .mcp.json + .grok-plugin manifest). This implements the Grok-side consumption (Task 6 of the implementation plan).
Quick Enable (recommended)
- Ensure you have the adapter repo at
~/projects/grok-nats-channel(or your preferred location). - In a Grok session, run:
(or/grok-nats-channel:launcher enable/grok-nats-channel:launcher enable /custom/path/to/grok-nats-channel) - The launcher skill creates the symlink
~/.grok/plugins/grok-nats-channel→ your path. - Grok will automatically pick up the MCP server definition from
.mcp.jsonand the/grok-nats-channel:configure+ launcher commands from theskills/directory. - Run
/grok-nats-channel:configureto pick your NATS context / session name / permission mode. - Restart your Grok TUI/session — the MCP spawns at startup.
- Verify:
grok mcp listshows "nats";nats micro lsshows agrservice; NATS prompts now arrive as<channel source="nats" ...>blocks with areplytool.
Manual / Advanced
- Symlink the plugin yourself:
mkdir -p ~/.grok/plugins ln -sfn ~/projects/grok-nats-channel ~/.grok/plugins/grok-nats-channel - Or register explicitly (if not using the plugin loader):
grok mcp add nats --command bun --args server.ts --cwd ~/projects/grok-nats-channel - Disable:
/grok-nats-channel:launcher disable rm -f ~/.grok/plugins/grok-nats-channel
Packaging note: We chose local plugin path (Option A) for v1/dev. Once published to npm under the @agent-ops scope (as @agent-ops/grok-nats-channel or equivalent), the launcher will also support bunx @agent-ops/grok-nats-channel and direct Grok marketplace installation (which will manage the plugin cache automatically). The skill names (/grok-nats-channel:*) are derived from the installed plugin directory and remain stable.
See skills/launcher/SKILL.md and skills/configure/SKILL.md for the full command surfaces.
