Configuration and Managed Clients
The administrator installs shared files. Each user supplies a private route credential through the harness’s launch environment. Prepare these files before starting the service runbook for your platform.
System Configuration
Relay resolves identity state in this order on every platform, including Windows:
- If
XDG_CONFIG_HOMEis set, use<XDG_CONFIG_HOME>/nemo-relay/daemon. - Otherwise, if
HOMEis set, use<HOME>/.config/nemo-relay/daemon. - Otherwise, if
USERPROFILEis set, use<USERPROFILE>/.config/nemo-relay/daemon.
On Windows, HOME takes precedence over USERPROFILE. Check the environment of
the actual daemon or client process before backing up its keys and trust pins.
The service runbooks set an explicit XDG_CONFIG_HOME for the daemon. It does
not change the managed worker’s system configuration path.
Client identities stay with the user and must not be shared or copied into a machine image.
For a new deployment, create config.toml with these contents:
Confirm provider URL fields against Provider Routing.
Use your approved provider URLs when they differ from these defaults. Set the same
routing on the daemon and clients for consistent pass-through behavior. The
primary nemo-relay daemon process reads only [daemon.logging]; [logging]
continues to configure user-space Relay processes such as agent launches. Do not
store users’ provider credentials in this shared file. Info-level logs expose
readiness events used by the runbooks; Relay’s default log level is error.
Relay waits for provider responses without a deadline by default, allowing long
compaction and model execution to finish. Caller cancellation ends the request.
Set [upstream] response_timeout_secs to a positive number of seconds to limit
the response-header wait on daemon and worker hops. Set it to 0 to disable
that limit. Connection, startup, and health checks retain their separate deadlines.
Create plugins.toml with the approved configuration from
Configure Plugins. A minimal file with no plugins is:
That minimal file is useful to bring up transport, but does not add observability or policy checks. For deployment acceptance, enable an approved exporter or policy and check its result. For example, use the ATOF exporter with a collector reachable by each client. Do not direct all user workers to one shared writable log file.
On Unix, create /etc/nemo-relay as root with mode 0755 and install the two
configuration files as root with mode 0644. On Windows, create
C:\ProgramData\nemo-relay as an administrator and allow users read access only.
If the directory is new, set its ACLs before distributing files:
After saving the examples as local config.toml and plugins.toml files,
install them on a new Unix deployment:
On Windows, copy them after setting the directory ACLs above:
For an existing installation, merge approved settings rather than replacing files or ACLs blindly. Managed workers ignore personal runtime configuration, plugin directories, and lifecycle state. Install required plugin binaries and dependencies in the administrator-managed locations referenced by the system manifest.
Provision Client Credentials
Provision one 32-byte random, base64url-encoded token for each machine-user identity. Keep the same token across that identity’s harness sessions. Do not regenerate it on every login. A token is not a model-provider key and does not replace the user’s normal provider login.
Create the Per-User Token File
The simplest provisioning path is the per-user token file. Run this command as the user who runs the coding agent, not as root or an administrator:
The command creates <user config dir>/.client-token if it does not already
exist. Relay resolves the user config directory the same way on every platform,
including Windows. It uses the first of these that is set:
$XDG_CONFIG_HOME/nemo-relay$HOME/.config/nemo-relay%USERPROFILE%\.config\nemo-relay
For example, on Linux and macOS the file is usually
~/.config/nemo-relay/.client-token. On Windows it is
%USERPROFILE%\.config\nemo-relay\.client-token unless XDG_CONFIG_HOME or
HOME is set, as it often is in Git Bash or MSYS shells. The file Relay uses is
the path value printed by nemo-relay daemon token ensure --json. The
command:
- Generates 32 bytes from the operating-system random source and writes them as one unpadded base64url line.
- Creates the parent directory with mode
0700when it does not exist and writes the file with mode0600(owner-only access on Windows). - Publishes the file atomically. Concurrent runs converge on one token.
- Leaves an existing valid file unchanged and reports
existing. - Never overwrites an existing invalid or unsafe file. It exits with an error naming the path and the problem; review and remove the file, then run the command again.
- Never prints the token.
--jsonprints only{"status":"created"|"existing","path":"..."}. - Refuses to run as root on Linux and macOS.
Fleet tooling can run the command at every login because it is idempotent. Do not copy one token file between users or machines.
Credential Resolution
Managed hooks (nemo-relay daemon hook), the managed MCP server
(nemo-relay daemon mcp), and nemo-relay doctor --managed-bundle resolve the
route credential in this order:
NEMO_RELAY_CLIENT_TOKEN, when it is set, nonempty, and valid.- The per-user token file, when it exists, is valid, and is safe: a regular file, not a symbolic link, owned by the current user, with no group or other permission bits (on Windows, the protected owner-only access list), and at most 128 bytes. One trailing newline is allowed.
- Otherwise, no credential.
An invalid environment value or an unsafe or invalid file is skipped with one redacted warning; it is not a hard error. The token value is never logged.
The token file exists because environment changes made after login, such as
launchctl setenv on macOS, never reach processes that were already running,
such as a terminal opened before the change. Every process of the same user can
read the file.
The token file restores hooks and MCP only. Harness model requests do not read
it. Each harness sends x-nemo-relay-client-token from its own provider
configuration, which is read when the harness starts:
- Codex reads
env_http_headers, sourced fromNEMO_RELAY_CLIENT_TOKEN, or a statichttp_headersentry for the managed provider. - Claude Code reads the header from
ANTHROPIC_CUSTOM_HEADERS, in its launch environment or its settingsenv.
Without that header, model requests use daemon pass-through (see
Requests Without a Bound Credential):
they still work but produce no Relay telemetry. A daemon started with
--require-worker rejects them with 401 instead. To attribute model traffic,
provide the header and restart the harness. For example, export it from the
token file in the login environment:
The managed v1 Pi extension reads only NEMO_RELAY_CLIENT_TOKEN and does not
read the token file. Pi fails to start without that environment variable:
its hooks, MCP, and model routing all stop, not just telemetry.
nemo-relay doctor --managed-bundle therefore rejects a file-only credential
for bundles that include Pi.
Provision Through Enterprise Tooling
Your enterprise secret service can also provision the token directly. For a small deployment without the token file, choose the commands for your platform.
Linux and macOS
Windows PowerShell
This example creates a private local bootstrap file once, without printing the token. It requires Python 3 and fails if the file already exists:
For later launches, run only the environment-loading lines, using the existing file. Use an administrator-provided launcher or login-environment mechanism to repeat that loading before starting the harness. Relay does not install a login agent. Launch desktop applications through that prepared environment; shell exports do not update an app that is already running.
The Claude header assignment above assumes no other custom headers are required.
If your deployment needs other headers, preserve those and ensure there is exactly
one x-nemo-relay-client-token entry with the matching token. Do not put the
credential in the shared bundle, command-line arguments, service unit, or logs.
Choose Stable Deployment Paths
The binary itself can be the stable dispatcher. The remote Linux example uses a small administrator-owned wrapper to supply worker network settings. Keep the chosen path fixed across binary upgrades. Dispatcher paths must contain no spaces or shell metacharacters, including on Windows. Bundle generation checks path shape; deployment tooling must enforce ownership and permissions.
On Windows, use forward slashes in --dispatcher-command. Generated hooks can
run in Git Bash, which treats backslashes as escape characters. PowerShell file
paths elsewhere in these examples can use backslashes.
Route Authentication
The managed topology has the following processes:
nemo-relay daemonowns the public LLM and hook endpoints and the authoritative route broker.nemo-relay daemon mcpregisters one MCP client reference for the current machine-user identity. It advertises no MCP tools.nemo-relay daemon hookforwards one native hook payload using the managed route credential.nemo-relay daemon workerruns the per-machine-user Relay configuration and remains attached to the daemon for its useful lifetime.
All LLM and hook paths remain at the daemon root. Managed settings do not use per-user route URLs. Requests use one Relay-specific public credential header:
Pi provider registrations also preserve Pi’s runtime-selected endpoint in
x-nemo-relay-upstream-base-url. The authenticated daemon consumes that
routing header and removes it before contacting the provider. It is runtime
model metadata, not part of the managed settings artifact.
Enterprise bootstrap can provide the credential through this environment
variable. Managed hooks and MCP also read the
per-user token file. The environment variable
is still required for the managed Pi extension, and for model requests whose
provider header is sourced from the environment: Codex env_http_headers, and
ANTHROPIC_CUSTOM_HEADERS derived from it.
Claude Code reads custom provider headers from its native environment variable, so the same bootstrap must derive exactly one header from that credential:
Do not append another x-nemo-relay-client-token entry to an existing custom
header value. Managed diagnostics reject a missing, duplicate, or mismatched
credential header when the credential comes from NEMO_RELAY_CLIENT_TOKEN.
When the credential comes from the token file, diagnostics accept a missing
ANTHROPIC_CUSTOM_HEADERS (Claude Code model requests then use pass-through)
but still reject a present header that does not match the file.
The same credential identifies MCP registration, Codex and Claude LLM requests, Pi provider requests, and managed hook requests. The daemon does not require a token file or a token environment variable. During signed MCP registration, it binds the credential digest to the user-machine fingerprint. An existing binding cannot be reassigned to another fingerprint. The daemon stores only credential digests, removes the public header before forwarding a request, and preserves the caller’s provider authentication.
Replace a Client Token
The daemon binds each token to the machine-user identity that first registers it. Bindings are kept in memory and are not released just because sessions end. A binding is cleared only when:
- the daemon restarts;
- the identity reaches its token limit and the token is its least recently registered one without a live MCP session (described below); or
- the daemon’s route table is full and the identity’s whole route is evicted because it has no MCP references.
If the token changes while the daemon is running, for example because the token
file is removed and nemo-relay daemon token ensure creates a new one:
- MCP registration with the new token joins the identity’s existing route and worker. No second worker is launched.
- The old token stays bound to that route alongside the new one, so sessions that still hold it keep working, until one of the events above clears it.
- Removing or purging a token file does not revoke a token the daemon has already bound. Use a daemon restart to clear every binding at once.
One identity can hold a limited number of distinct tokens at once: 4 by default. The limit bounds the daemon’s in-memory token table; it is not a security control. When a new token arrives at the limit:
- The daemon releases the identity’s least recently registered token that has
no live MCP session and logs
route_token_released. A released token resolves like any unbound token and can bind again later. - If every bound token still has a live MCP session, the new token is rejected
with
route_credential_rejected, and the daemon logsmcp_registration_rejectedwithreason = "route_token_limit_reached".nemo-relay daemon mcpdoes not retry the rejection; it logsdaemon_mcp_pass_throughwithreason = "credential_rejected"and keeps serving MCP without a route, so the harness keeps working. The same applies when an already running MCP registers again after a reconnect or daemon restart: it logs the fallback withphase = "reregistration"and keeps serving instead of exiting. The fallen-back MCP does not register again, so freeing a slot or restarting the daemon does not bind its token. Requests with that token use pass-through, or receive401with--require-worker, until a new MCP session with the token registers. Restart the affected harness session to bind it once a slot is free.
Change the limit with nemo-relay daemon --max-tokens-per-identity <N> or the
NEMO_RELAY_DAEMON_MAX_TOKENS_PER_IDENTITY environment variable on the daemon
service, from 1 through 64. The flag takes precedence. An invalid environment
value stops daemon startup with a configuration error. Set the variable in the
service definition, for example EnvironmentVariables in the macOS launchd
plist, Environment= in a systemd unit, or the WinSW service environment, then
restart the daemon. Raise it only if users legitimately run sessions on more
than four tokens at the same time; idle tokens are released automatically.
Requests Without a Bound Credential
A missing credential means telemetry and guardrails are skipped for that request; the coding agent keeps working:
- A public request with no
x-nemo-relay-client-tokenheader, or with a well-formed credential that no MCP session has bound (for example, after a daemon restart and before MCP registers again), uses pass-through. So does a bound credential whose route has no live MCP session, for example from a harness whose MCP exited while the daemon was restarted or reinstalled. Hook requests receive the agent’s no-op pass-through response. Provider requests are forwarded directly to the configured provider with the caller’s own provider authentication. - A malformed credential or a repeated header still returns
401. - A daemon started with
--require-workerkeeps rejecting missing and unbound credentials with401. - A request without a bound credential never receives daemon-held provider
credentials (configured provider auth headers or the daemon’s own
OPENAI_API_KEYorANTHROPIC_API_KEY), except that a daemon explicitly started with--pass-throughapplies its provider credentials to every request. - A provider request that names its own upstream with
x-nemo-relay-upstream-base-url, as the managed Pi extension does, is honored only with a bound credential. Without one it receives503withRetry-Afterand is never forwarded, so a credential meant for the named upstream cannot reach the configured provider. After a daemon restart, retry once MCP registers again. The daemon logspublic_request_named_upstream_rejected. - The daemon logs each decision as
public_request_pass_throughwithroute_mode = "pass_through"andreason = "missing_credential",reason = "unbound_credential", orreason = "no_live_session", rate-limited, without the credential.
On the client side, nemo-relay daemon hook with no resolvable credential does
not contact the daemon. It writes the same pass-through response and exits 0,
regardless of --fail-open or --fail-closed, and logs a warning.
nemo-relay daemon mcp with no resolvable credential keeps serving the MCP
protocol without registering a route, so hosts that require the server keep
running. It does the same, without retrying, when the daemon rejects its
credential at registration; see Replace a Client Token.
Enrollment is open to anyone who can reach the daemon and prove possession of their machine identity. This proof establishes identity, not organizational authorization. Restrict daemon access to trusted users through your network or authenticated reverse proxy; do not expose open enrollment to an untrusted network. TLS remains required for non-loopback communication.
Challenge requests are limited to 16 per transport-peer IP in a 15-second window, before the request body is read. MCP and worker challenges retain separate capacity limits. Forwarded IP headers do not override this limit: clients behind a reverse proxy share its peer budget. Apply additional per-client admission controls at the proxy for larger deployments.
Distribute Immutable Managed Settings
A managed bundle is an administrator-owned deployment artifact, not an output
of personal nemo-relay install. For each agent, platform, and deployment, its
plugin configuration and settings must remain byte-for-byte identical across
users and Relay binary releases.
The bundle follows these rules:
- Daemon and provider URLs and hook command text are fixed for the deployment.
- Artifacts contain no user path, fingerprint, credential, generation ID, or Relay binary version.
- A stable administrator-owned dispatcher command or path survives Relay upgrades.
- Refresh validates immutable artifacts and never rewrites them.
- An incompatible settings change uses a separately named v2 artifact rather than replacing v1 bytes.
Enterprise bootstrap owns credential provisioning and login-environment
injection. Relay validates the resolved route credential from
NEMO_RELAY_CLIENT_TOKEN or the per-user token file, and
nemo-relay daemon token ensure creates that file, but Relay does not install
an operating-system-specific login agent.
Create a bundle once from fixed deployment values. Repeat --agent to select
the artifacts that the administrator distributes:
Run bundle generation as an administrator when writing the installed system
path. For macOS, change --platform linux to --platform macos. For remote
Linux, use --daemon-address https://relay.example.com:8443 and
--dispatcher-command /opt/nvidia/bin/nemo-relay-dispatch.
On Windows, use an elevated PowerShell window:
The installed bundle must be readable by users and writable only by administrators. Store the printed digest in your deployment records and provision it separately to clients. Do not add files to the bundle after generation, even a digest file or a marketplace manifest; its exact file set is validated.
The destination must be new or already byte-identical. The command never rewrites a different v1 bundle and prints only the canonical full-bundle SHA-256. Provision that digest separately from the bundle; a digest stored inside the same artifact is not a trust root. The dispatcher path is validated for the target platform and must be an absolute, stable administrator path outside known user and temporary directories.
Validate a distributed bundle and its current environment without modifying the bundle:
Doctor compares the exact file set and bytes with the canonical manifest and the separately provisioned digest, then reports missing, unexpected, or changed artifacts. This is a managed-only diagnostic: it does not load or fail because of personal configuration, plugins, or agent installations.
Load a Managed Harness
Install only the managed Relay integration for this deployment. Disable or remove the personal Relay plugin through its harness before switching. Two Relay MCP entries or two sets of Relay hooks can conflict or submit events twice.
The following steps load the generated bytes. They do not make a user’s entire harness configuration immutable. For organization-wide enforcement, distribute these artifacts with the harness’s managed policy mechanism and verify its effective settings. Keep unrelated settings and existing authentication intact.
Codex
Codex needs both the generated plugin and the provider configuration. Create an
administrator-owned local marketplace beside the validated bundle. On Linux
and macOS, use /opt/nvidia/share/nemo-relay-codex-marketplace-v1; on Windows, use
C:\ProgramData\NVIDIA\NeMoRelay\codex-marketplace-v1.
Inside that marketplace, create .agents/plugins/marketplace.json:
Copy the entire bundle directory codex/plugin-v1 to the marketplace’s
plugins/nemo-relay-managed-v1, including hidden files. On Unix:
On Windows, create the corresponding directories in an elevated PowerShell window:
Run the copy only when creating this marketplace. Keep its files administrator-owned. For subsequent deployments, compare the copy with the validated bundle rather than merging new files into it. Save the JSON manifest at the path given above.
As the target user, register and enable the plugin:
On Windows, use the same commands with the quoted Windows marketplace path.
These commands match the Codex plugin add interface; confirm the installed
harness meets the repository’s minimum version.
Load codex/settings-v1/config.toml from the bundle into the deployed Codex
configuration. For a fresh user configuration, copy its contents to
~/.codex/config.toml (Windows: %USERPROFILE%\.codex\config.toml). If a config
already exists, back it up and merge the top-level model_provider setting and
the [model_providers.nemo-relay-managed-v1] table once. Keep authentication and
unrelated configuration. Do not append duplicate TOML tables. If CODEX_HOME is
already configured, use that configuration location instead.
For ChatGPT voice calls, add this setting to your user-level Codex configuration
or /etc/codex/managed_config.toml. Put it at the top level, before any TOML
table headers. Use your daemon’s address if it differs from this local example.
This URL makes Codex send ChatGPT’s JSON call request to /realtime/calls.
The updated daemon forwards that request through the selected worker.
Keep the managed provider’s existing /v1 base URL. API-key sessions use the
public API format and do not need this ChatGPT setting.
Restart Codex Desktop after changing its configuration. The default WebRTC
sideband can connect directly to OpenAI. The audio also travels directly.
For this setup, you do not need to set experimental_realtime_ws_base_url.
Keep this setting in the source configuration used by your deployment tools
so they preserve it during updates. Do not edit the immutable
codex/settings-v1/config.toml bundle artifact. Updating the binary or running
managed refresh does not add this setting. The personal-install
command nemo-relay install codex --force does not update a managed deployment.
Enable hooks in that effective configuration:
Merge hooks = true into an existing [features] table when present. Restart
Codex from the prepared token environment. Open /mcp and confirm the Relay MCP
connection is ready. Review and trust the installed Relay hooks through Codex’s
hook controls; verify exactly one enabled handler for every generated hook event.
A plugin listing alone does not prove that hooks are trusted or the provider is
selected. See Codex configuration
for configuration layers and Operations for end-to-end checks.
Claude Code
Launch Claude Code with both generated artifacts. On Linux and macOS:
On Windows:
Use these arguments in the managed launcher for every session. The filename
managed-settings.json does not itself enforce policy when passed with
--settings; enforced deployments must install it through Claude Code’s managed
settings mechanism. Preserve any existing organization policy when merging its
env.ANTHROPIC_BASE_URL value. The plugin and its MCP/hook files remain unchanged.
See Claude Code’s plugin loading guide
and settings reference.
Hooks and MCP can use the per-user token file when the launch environment has
no NEMO_RELAY_CLIENT_TOKEN. Gateway model routing still needs
ANTHROPIC_CUSTOM_HEADERS with the matching token, set in the launch environment
or in Claude Code settings env. Without it, Claude model requests pass through
the daemon without Relay telemetry, or receive 401 from a daemon started with
--require-worker. When both variables are set, their tokens
must match. Check /mcp after startup and verify that Relay hooks are
enabled. Managed Claude model routing requires an Anthropic API key or an
administrator-configured Anthropic-compatible gateway credential. Claude.ai OAuth
is not supported with ANTHROPIC_BASE_URL: Claude Code refreshes that session
against its native service rather than an Anthropic-compatible gateway. Do not set
the managed base URL for a Claude.ai OAuth-only deployment; preserve native routing
until Anthropic provides a supported OAuth gateway contract.
Pi
Use the managed Pi extension below on Unix. On Windows, the equivalent launch command is:
Always include --no-extensions and the exact administrator-owned extension
path. Verify a model whose provider uses one of the supported HTTP APIs. Pi
starts the managed MCP process itself; do not register a second Relay MCP process.
Deploy the Managed Pi Extension
The Pi artifact is a fixed TypeScript extension under
pi/extension-v1/index.ts. Load that exact administrator-installed file while
disabling discovered extensions; do not copy it into a per-user extension
directory or rewrite it during upgrades. For example:
--no-extensions is mandatory for managed launches. Pi still loads the
explicit -e extension, but does not also load user, project, or discovered
extensions that could alter provider registration, tool arguments, or shell
policy after Relay has authorized an operation.
The extension starts the fixed dispatcher as
daemon mcp --daemon-address <URL>, waits for MCP initialization before
registering managed providers, and
keeps one process-wide broker reference across Pi reload, new, resume, and fork
transitions. It sends Pi’s session, agent, turn, compaction, tool, and custom
shell events to /hooks/pi; policy responses are converted back into Pi’s
native tool_call and user_bash blocking results. Any selected Pi provider
whose models use only OpenAI Completions, OpenAI Responses, or Anthropic
Messages and share one endpoint is registered against the daemon, including
custom provider names. The registration attaches x-nemo-relay-client-token
and Pi’s exact runtime-selected endpoint in
x-nemo-relay-upstream-base-url. No per-user upstream, fingerprint,
generation, user path, or route value is written into the managed artifact.
Supported Provider Routes
The managed daemon accepts provider requests for only these API types:
- OpenAI Completions
- OpenAI Responses
- Anthropic Messages
The daemon does not have a route for Google’s Gemini or Vertex API. Requests to those normal Google endpoints do not pass through Relay, so Relay cannot record the LLM call or apply LLM request rules to it.
To send Gemini model traffic through the daemon, use a provider or proxy that accepts OpenAI Completions, OpenAI Responses, or Anthropic Messages requests, then configure the client to use that compatible API. Do not point a Gemini endpoint at the daemon’s OpenAI or Anthropic routes; the request formats are different. Relay’s Gemini codec can translate Gemini data in application integrations, but it does not add a managed daemon route.
Codex Responses Compatibility
Both the personal gateway and managed daemon recognize Codex ChatGPT OAuth on
OpenAI routes, including the managed provider’s /v1/responses and /v1/models
requests. When no administrator OpenAI credential is configured, Relay preserves
the OAuth credential and sends the request to the native ChatGPT Codex backend.
When openai_auth_header or OPENAI_API_KEY is configured for the daemon, Relay
removes the ChatGPT OAuth credential, injects that replacement credential, and
uses the administrator’s configured OpenAI-compatible upstream instead.
ChatGPT voice call creation is an exception. Requests to
POST /backend-api/codex/realtime/calls keep the caller’s ChatGPT credential
and use the native ChatGPT backend even when a replacement OpenAI credential
is configured. This preserves ChatGPT’s JSON call format.
Live and Realtime connections use the configured OpenAI upstream and keep the
caller’s provider credential. By default, that upstream is api.openai.com, which matches Codex’s WebRTC sideband destination.
Relay does not replace a ChatGPT sideband credential with an administrator key.
The daemon also accepts Codex’s ChatGPT-shaped
POST /backend-api/codex/responses path and canonicalizes it to the ordinary
Responses route. A WebSocket upgrade probe sent with GET to that path,
/responses, or /v1/responses receives 426 Upgrade Required, which causes
clients that support the fallback to use HTTP streaming. An ordinary GET remains
405 Method Not Allowed.