diff --git a/README.md b/README.md index ecbca300a..5a9f5facc 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ trajectory file, you have concrete data to inspect, debug, and build on. ### Local Agent Trajectory This walkthrough shows an end-to-end quick success setup. Install the -`nemo-relay-cli`, turn on local exporters, run either Codex or Claude Code +`nemo-relay-cli`, turn on local exporters, run Codex, Claude Code, or Hermes through Relay, and check that Relay wrote both raw events and normalized trajectories. @@ -83,13 +83,12 @@ The editor creates or updates the nearest project plugin file at then configure these sections: 1. Toggle the Observability component on. -2. Open `ATOF`. Toggle the section `[on]`. +2. Open **ATOF**. Toggle the section **[on]**. - Optionally set: - - `output_directory` to `.nemo-relay/atof` - - `filename` to `events.jsonl` - - `mode` to `overwrite` -3. Open `ATIF`. Toggle the section `[on]`. + Add a file sink. You can set its `output_directory` to + `.nemo-relay/atof`, `filename` to `events.jsonl`, and `mode` to + `overwrite`. Add stream sinks to send the same events to remote collectors. +3. Open **ATIF**. Toggle the section **[on]**. Optionally set: - `output_directory` to `.nemo-relay/atif` @@ -101,7 +100,7 @@ then configure these sections: > Run `nemo-relay plugins edit` without `--project` only when you want > user-level exporter settings that apply across projects. -#### 3. Run Codex or Claude Code Through Relay +#### 3. Run a Coding Agent Through Relay Run the Relay wrapper for the host CLI installed on your machine. For example: @@ -109,10 +108,18 @@ Run the Relay wrapper for the host CLI installed on your machine. For example: nemo-relay codex -- exec "Summarize this repository." ``` +For Claude Code, run: + ```bash nemo-relay claude -- "Summarize this repository." ``` +For Hermes, run: + +```bash +nemo-relay hermes -- -z "Summarize this repository." +``` + Refer to the full [Quick Start CLI](https://docs.nvidia.com/nemo/relay/nemo-relay-cli/about) docs for more options. The transparent wrapper starts a local Relay gateway, injects host-specific hook @@ -120,10 +127,18 @@ and provider settings for that launched process, then shuts the gateway down when the agent exits. > [!WARNING] -> If generated hooks are inactive, Codex users must review and activate them -> before events appear. The Codex Desktop App has additional limitations. -> Refer to the [Codex CLI guide](https://docs.nvidia.com/nemo/relay/nemo-relay-cli/codex) for the -> current hook activation caveat and troubleshooting steps. +> The transparent wrapper trusts only the Codex hooks that it generates for the +> launched process. A persistent `nemo-relay install codex` trusts only the +> hooks owned by `nemo-relay-plugin@nemo-relay-local`; manual and source +> marketplace installs can still require review. Restart an open Codex app after +> persistent installation. +> +> On Windows, a restrictive host Job Object can keep the shared Relay gateway +> within the host process lifetime. If the host also rejects the required nested +> assignment, persistent bootstrap stops and explains the conflict. The Codex +> desktop app has additional limitations. Refer to the +> [Codex CLI guide](https://docs.nvidia.com/nemo/relay/nemo-relay-cli/codex) for +> lifecycle, startup, and troubleshooting details. #### 4. Verify the Run @@ -287,8 +302,8 @@ coverage. | Agent | Observability | Security | Optimization | Notes | |:--|:--:|:--:|:--:|:--| | Claude Code | Yes | Yes | Partial | Hook forwarding, pre-tool blocking, and gateway-routed LLM observability are supported. | -| Codex | Yes | Yes | Partial | Hook activation is required; missing session-end behavior limits trajectory finalization and full optimization coverage. | -| Hermes Agent | Yes | Yes | Partial | Hook forwarding, pre-tool blocking, and gateway-routed or hook-backed LLM observability are supported. | +| Codex | Yes | Yes | Partial | Persistent install verifies the exact plugin hooks. Each `Stop` finalizes a turn snapshot; the supported generated schema does not install `SessionEnd`. | +| Hermes Agent | Yes | Yes | Partial | User config installs the shared native MCP gateway lifecycle plus exact trusted hooks; gateway-routed or hook-backed LLM observability is supported. | ### Public API Integrations diff --git a/crates/cli/README.md b/crates/cli/README.md index b5436a5d6..443d3003d 100644 --- a/crates/cli/README.md +++ b/crates/cli/README.md @@ -26,19 +26,23 @@ with the installed `nemo-relay` command rather than link against the crate. ## Why Use It? +The CLI is designed for these tasks: + - **Observe existing coding agents**: Run Claude Code, Codex, or Hermes Agent through a local NeMo Relay gateway without changing the agent itself. -- **Configure hooks interactively**: Use the setup wizard to write project or - user config and install the hook files needed by supported agents. +- **Configure transparent runs interactively**: Use the setup wizard to write + project or user configuration for supported agents. - **Export local sessions**: Write ATIF trajectory files, ATOF event JSONL streams, or OpenInference spans from one shared config model. - **Diagnose setup readiness**: Check config layers, `plugins.toml` discovery, - agent binaries, persistent host-plugin installs, hook status, observability - outputs, and shell completions with `nemo-relay doctor`. + agent binaries, persistent coding-agent integrations, hook status, + observability outputs, and shell completions with `nemo-relay doctor`. ## What You Get +The CLI provides these capabilities: + - **`nemo-relay` binary**: The executable installed by the `nemo-relay-cli` Cargo package. - **First-run setup**: Bare `nemo-relay` launches setup when no config exists, @@ -49,6 +53,12 @@ with the installed `nemo-relay` command rather than link against the crate. CLI overrides for deterministic non-interactive use. - **Hook forwarding server**: A local gateway accepts agent hook events and provider-shaped OpenAI or Anthropic requests. +- **Persistent agent integration**: `nemo-relay install` configures Codex, + Claude Code, or Hermes Agent with one generated MCP bootstrap and the host's + canonical lifecycle hooks. +- **Shared gateway lifecycle**: Every persistent integration launches the same + host-neutral `nemo-relay mcp` client. Concurrent clients share one native + gateway on `127.0.0.1:47632`. ## Installation Options @@ -101,6 +111,12 @@ nemo-relay codex nemo-relay claude -- "summarize this repository" ``` +Install persistent integrations for the supported agent CLIs on `PATH`: + +```bash +nemo-relay install all +``` + Use `run --dry-run` to inspect resolved config without spawning the agent: ```bash diff --git a/docs/build-plugins/dynamic-plugins/grpc-worker/python/about.mdx b/docs/build-plugins/dynamic-plugins/grpc-worker/python/about.mdx index 6317330a5..79d20f405 100644 --- a/docs/build-plugins/dynamic-plugins/grpc-worker/python/about.mdx +++ b/docs/build-plugins/dynamic-plugins/grpc-worker/python/about.mdx @@ -211,6 +211,14 @@ print(f"sha256:{sha256(artifact.read_bytes()).hexdigest()}") PY ``` +The module in `load.entrypoint` must resolve directly under +`source.manifest_root` to exactly one Python source file: either +`module/path.py` or `module/path/__init__.py`. That file must also be the +integrity-checked `source.artifact`. Relay rejects ambiguous modules and custom +build-backend mappings, including `src/` layouts that it cannot derive from the +manifest. This check prevents an unsigned sibling module from becoming the +executed entrypoint. + ## Register the Plugin Run the following commands from the project directory: @@ -227,6 +235,13 @@ the `source.manifest_root` project into it. Relay uses that recorded environment when it starts the worker. Do not start the worker directly: Relay supplies its worker socket, host socket, activation ID, and activation token. +Relay records a digest of the installed environment in a locally authenticated +marker that is bound to the integrity-checked entrypoint artifact. The marker +detects changes between provisioning and activation. It is not a security +boundary against another process running as the same operating-system user, +because that process can access the same user-owned keys and files. Worker +process isolation is not a security sandbox. + ## Configure the Plugin After `plugins add` registers the worker, edit diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx index 45bd1b363..fd1cfd8ca 100644 --- a/docs/getting-started/installation.mdx +++ b/docs/getting-started/installation.mdx @@ -61,16 +61,17 @@ inherit the persistent `PATH` change. Verify that the command is available: nemo-relay --version ``` -After installation, `nemo-relay` can also install persistent Claude Code and -Codex host plugins: +After installation, `nemo-relay` can also install persistent Claude Code, +Codex, and Hermes Agent integrations: ```bash nemo-relay install claude-code nemo-relay install codex +nemo-relay install hermes ``` -Refer to [Plugin Installation](/nemo-relay-cli/plugin-installation) for the host -plugin workflow. +Refer to [Coding Agent Installation](/nemo-relay-cli/plugin-installation) for +the persistent integration workflow. The installers download the published SHA-256 checksum for the selected release @@ -230,17 +231,18 @@ configuration and verification steps. ### Hermes Agent -Install the latest NeMo Relay Python package in the environment that runs -Hermes, then enable the bundled Hermes plugin: +After installing the NeMo Relay CLI, install and diagnose the Hermes user +integration: ```bash -pip install nemo-relay -hermes plugins enable observability/nemo_relay +nemo-relay install hermes +nemo-relay doctor --plugin hermes ``` -The plugin ID is `observability/nemo_relay`. See the -[Hermes NeMo Relay plugin guide](https://github.com/NousResearch/hermes-agent/tree/main/plugins/observability/nemo_relay) -for configuration and verification steps. +Relay adds a native stdio MCP entry and trusts only the lifecycle hooks that it +installs. The MCP process acquires the shared user-level gateway when it starts. +Refer to the [Hermes Agent guide](/nemo-relay-cli/hermes) for provider routing, +lifecycle, and removal steps. ### Python Framework Integrations diff --git a/docs/getting-started/quick-start/index.mdx b/docs/getting-started/quick-start/index.mdx index f1152f1e4..29e61b57c 100644 --- a/docs/getting-started/quick-start/index.mdx +++ b/docs/getting-started/quick-start/index.mdx @@ -26,7 +26,7 @@ not yet know which guide owns the working path. | Layer | Use When | Start Here | Success Check | |---|---|---|---| | CLI and gateway | A coding-agent harness owns invocation and provider routing | [CLI Basic Usage](/nemo-relay-cli/basic-usage) | The wrapped agent runs with Relay active and emits hook output, plus gateway-routed LLM lifecycle output when provider routing is active. | -| Persistent host-plugin installs | You want the maintained install path for Codex or Claude Code instead of the transparent wrapper | [Plugin Installation](/nemo-relay-cli/plugin-installation) | `nemo-relay doctor --plugin ` confirms the installed host plugin is registered and ready. | +| Persistent coding-agent installs | You want the maintained install path for Codex, Claude Code, or Hermes Agent instead of the transparent wrapper | [Coding Agent Installation](/nemo-relay-cli/plugin-installation) | `nemo-relay doctor --plugin ` confirms the installed integration is ready. | | Direct Python or Node.js application APIs | Your application owns the tool or LLM callback | [Python Quick Start](/getting-started/quick-start/python) or [Node.js Quick Start](/getting-started/quick-start/nodejs) | The sample prints event lines plus tool and LLM results. | | Direct Rust application APIs | Your Rust application owns the tool or LLM callback | [Rust Quick Start](/getting-started/quick-start/rust) | The sample prints scope, tool, and LLM lifecycle output plus the `initialized` mark event. | | Plugin-managed runtime setup | You need process-level exporter or plugin behavior from `plugins.toml` | [Plugin Configuration Files](/configure-plugins/plugin-configuration-files) | The selected plugin path activates and writes the expected output or behavior. | diff --git a/docs/nemo-relay-cli/about.mdx b/docs/nemo-relay-cli/about.mdx index 32211aba7..5481c5330 100644 --- a/docs/nemo-relay-cli/about.mdx +++ b/docs/nemo-relay-cli/about.mdx @@ -20,6 +20,13 @@ model traffic through the gateway, and diagnose local configuration. The `nemo-relay-cli` Cargo package remains available for source-based installation and unsupported platforms. +Persistent integrations use one lifecycle path: + +`Codex / Claude Code / Hermes Agent` → `nemo-relay mcp` → `shared Relay gateway` + +The host-specific layer installs and emits canonical lifecycle hooks. The MCP +process is host-neutral and keeps the shared user-level gateway available. + ## Start Here When Use these guides when you need to: @@ -27,8 +34,8 @@ Use these guides when you need to: - Observe Claude Code, Codex, or Hermes Agent sessions locally. - Configure coding-agent hooks for NeMo Relay lifecycle events. - Route model-provider traffic through the local NeMo Relay gateway. -- Install persistent Claude Code or Codex host plugins without wrapping the - agent command. +- Install persistent Claude Code, Codex, or Hermes Agent integrations without + wrapping the agent command. - Validate and install model pricing catalog sources for local cost estimates. - Export local sessions to Agent Trajectory Interchange Format (ATIF), Agent Trajectory Observability Format (ATOF) JSONL, OpenTelemetry, or @@ -49,8 +56,8 @@ controls. | Agent | Observability | Security | Optimization | Notes | | --- | --- | --- | --- | --- | | Claude Code | Yes | Yes | Partial | Pre-tool hook responses are supported. LLM optimization uses gateway-routed traffic; full coverage depends on loaded Claude Code hooks. | -| Codex | Yes | Yes | Partial | Hook forwarding and gateway-routed LLM optimization are supported after hooks are reviewed and activated. The missing session-end hook limits full coverage. | -| Hermes Agent | Yes | Yes | Partial | Hook forwarding, pre-tool guardrails, and Hermes API-request telemetry are supported. Optimization depends on Hermes shell and API-request hook coverage. | +| Codex | Yes | Yes | Partial | Persistent install verifies all 10 hooks in the supported schema. Each `Stop` finalizes a turn snapshot because the plugin schema does not expose `SessionEnd`. | +| Hermes Agent | Yes | Yes | Partial | User config installs the shared native MCP gateway lifecycle plus exact trusted hooks. Optimization depends on Hermes shell and API-request hook coverage. | ## Guides @@ -58,16 +65,15 @@ Use these guide links to move from CLI setup into agent-specific instructions. - [Basic Usage](/nemo-relay-cli/basic-usage) explains gateway routes, transparent runs, shared configuration, hook forwarding, and runtime mapping. -- [Plugin Installation](/nemo-relay-cli/plugin-installation) covers persistent - Claude Code and Codex plugin setup through host marketplaces. +- [Coding Agent Installation](/nemo-relay-cli/plugin-installation) covers + persistent Claude Code, Codex, and Hermes Agent setup. - [Claude Code](/nemo-relay-cli/claude-code) covers transparent Claude Code runs, Anthropic gateway routing, ATIF verification, and unsupported Claude application modes. - [Codex](/nemo-relay-cli/codex) covers transparent Codex CLI runs, local GUI/app caveats, model provider routing, and remote-task limits. -- [Hermes Agent](/nemo-relay-cli/hermes) covers Hermes shell hook installation, - dynamic gateway URL handling, session-finalize behavior, and hook consent - caveats. +- [Hermes Agent](/nemo-relay-cli/hermes) covers Hermes MCP lifecycle setup, + exact hook trust, provider routing, and session-finalize behavior. Start with [Basic Usage](/nemo-relay-cli/basic-usage), then use the guide for the coding agent that you want to observe. diff --git a/docs/nemo-relay-cli/hermes.mdx b/docs/nemo-relay-cli/hermes.mdx index 28e81fb4f..8fadf3fa8 100644 --- a/docs/nemo-relay-cli/hermes.mdx +++ b/docs/nemo-relay-cli/hermes.mdx @@ -1,207 +1,229 @@ --- title: "Hermes Agent" -description: "" +description: "Configure Hermes to start and share the native NeMo Relay gateway through MCP." position: 7 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} -The NeMo Relay CLI wrapper and gateway allows you to observe local Hermes Agent sessions. The guide below shows you how to observe these sessions. +NeMo Relay can observe Hermes through a persistent integration or a transparent +run. The persistent integration uses the Hermes native studio MCP support to own a +shared Relay gateway at `127.0.0.1:47632`. Shell hooks forward the Hermes native +session, subagent, tool, and model lifecycle payloads to that gateway. If you +also point the Hermes provider at the gateway, Relay observes the model requests +and responses. -This path is different from the upstream Hermes -`observability/nemo_relay` plugin. The CLI wrapper observes Hermes through -Hermes shell hooks. It can record LLM request/response payloads from sanitized -Hermes API hook payloads when the Hermes build provides them, or from provider -traffic routed through the NeMo Relay gateway. The upstream Hermes plugin runs -inside Hermes and is configured from Hermes itself. +The MCP process manages gateway lifetime through a shared lifecycle lease. You +do not need a separate service manager, wrapper process, Node.js runtime, or +Python bootstrap component. -Hermes shell hooks provide session, subagent, tool, and LLM lifecycle signals. -For hook-based LLM lifecycle telemetry, `pre_api_request`, `post_api_request`, -and `api_request_error` are the authoritative Hermes hooks. The legacy -`pre_llm_call` and `post_llm_call` hooks still exist, but only as private -hint-style signals for correlation. When Hermes hook payloads include sanitized -request and response bodies, NeMo Relay records them from the hooks. Gateway -routing is still the direct provider-traffic path, and it remains the best -fallback when a Hermes build emits summary-only hook payloads. +Use persistent capture for ordinary `hermes` commands. Use a transparent run +when you need project-specific Relay configuration or an isolated gateway for +one Hermes process. -## Choose the Right Hermes Path +## Install Persistent Capture -Use the `nemo-relay hermes` wrapper when you want NeMo Relay to manage the -local gateway lifetime for a Hermes process and collect hook plus -gateway-routed LLM observability. - -Use the upstream Hermes `observability/nemo_relay` plugin when you want Hermes -itself to load the bundled plugin and emit NeMo Relay observability through -Hermes plugin configuration. Observe-only plugin builds keep Hermes in control -of LLM and tool execution. - -Use adaptive execution only with a Hermes build that includes the adaptive -middleware contract and a NeMo Relay runtime that exposes managed -`llm.execute(...)` and `tools.execute(...)` boundaries. Verify the Hermes -release tag before depending on adaptive execution in a released Hermes -environment. - -## Transparent Run - -Use the wrapper when you want the gateway lifetime managed for a local Hermes -process: +Install the user-level integration: ```bash -nemo-relay hermes +nemo-relay install hermes ``` -Pass Hermes arguments after `--`: +Installation requires Hermes Agent 0.18.2 or newer. NeMo Relay checks the +installed Hermes CLI before it changes any files. -```bash -nemo-relay hermes -- chat --provider custom -``` +Relay preserves unrelated Hermes settings and updates the Relay-owned portions +of the user configuration. Hermes reads this configuration from +`$HERMES_HOME/config.yaml`, or `~/.hermes/config.yaml` when `HERMES_HOME` is +unset. This location is user-owned even when you choose project-scoped Relay +configuration. + +The MCP server name `nemo-relay` is reserved for the Relay-managed entry. If +that name already belongs to another command, installation stops without +rewriting the config. Rename or remove the conflicting entry, then retry. + +The installer makes the following changes: -After NeMo Relay config exists, this shortcut is equivalent to -`nemo-relay run --agent hermes`. The wrapper starts a gateway on a dynamic -`127.0.0.1` port and exports `NEMO_RELAY_GATEWAY_URL` for the launched -process. After initial NeMo Relay setup exists, Hermes hook configuration is -temporary in this mode: the launcher merges the NeMo Relay hook-forward -commands into the configured Hermes hook file for the run and restores the -original file afterward. The wrapper also sets `HERMES_ACCEPT_HOOKS=1` so -Hermes can use the injected hook commands without extra manual approval -prompts. +- Adds `mcp_servers.nemo-relay`, which runs the canonical absolute + `nemo-relay` executable with the `mcp` argument. +- Binds the shared gateway to `127.0.0.1:47632` and adds a private generation + marker that prevents stale MCP clients and hooks from using a replacement + installation. +- Installs one Relay handler for each supported Hermes lifecycle event. +- Records each exact event and command pair in + `shell-hooks-allowlist.json`. +- Migrates a complete legacy Relay entry, removing its old hook commands and + approvals while preserving unrelated hooks, MCP servers, approvals, and + Hermes settings. -If no NeMo Relay config exists yet, `nemo-relay hermes` triggers the setup flow -first and then launches Hermes through the same wrapped pipeline. +NeMo Relay writes the Hermes config, allowlist, and generation marker as one +verified transaction. If a write or verification fails, it rolls those files +back to their original state. It also locks concurrent integration updates so +they cannot overwrite one another. -Inspect what would be launched without starting Hermes: +After installation, run the integration doctor: ```bash -nemo-relay run \ - --dry-run \ - --print \ - -- hermes +nemo-relay doctor --plugin hermes ``` -## Shared Config +The doctor checks the Hermes version, Relay executable, MCP entry, lifecycle +hooks, trust records, generation marker, and forwarded environment names. + +The generated MCP entry always forwards the standard Relay, OpenTelemetry, AWS, +proxy, certificate, and provider credential variable names. It writes each one +as a `${VARIABLE_NAME}` reference instead of copying a secret value into Hermes +configuration. Before parsing the MCP command line, Relay treats an unresolved +self-reference as unset. If you later add a custom `NEMO_RELAY_`, `OTEL_`, or +`AWS_` variable or change the name of a credential variable referenced by +plugin configuration, run `nemo-relay install hermes --force` to refresh the +MCP environment. + +## Gateway Lifecycle + +Hermes launches `nemo-relay mcp` as a long-lived stdio MCP server. The server +does not expose tools. Before it answers the MCP initialization request, it +starts or joins a gateway and verifies the Relay identity, version, bootstrap +protocol, and effective persistent configuration. + +Hermes starts MCP discovery in the background, so an early lifecycle hook can +arrive before the MCP process finishes acquiring the gateway. The installed +hook waits for that MCP-owned gateway, verifies it, and forwards the original +payload once. It never launches or recovers Relay. + +Concurrent Hermes, Claude Code, and Codex MCP clients can share one compatible +gateway. Each open client keeps a liveness lease by heartbeating the gateway. A +startup lock and recovery record let overlapping clients coordinate one +restart. If the replacement fails again, the MCP clients exit with an error. +When no MCP clients or active sessions remain, the gateway exits after 300 +seconds of inactivity by default. Set `NEMO_RELAY_PLUGIN_IDLE_TIMEOUT_SECS` to a +different positive timeout in seconds before setup and launch. + +Persistent MCP mode reads only system and user Relay configuration. It does not +load a project's `.nemo-relay` layer. Use transparent run mode for +project-specific Relay configuration. + +NeMo Relay removes or replaces Hermes MCP entries, hooks, and approvals only +when it can identify them as managed. A current MCP entry must use the expected +executable, `mcp` argument, shared bind, and generation marker. A legacy entry +must use the same canonical Relay executable in its MCP entry and complete hook +set. NeMo Relay preserves partial or manual lookalike entries. Rename or remove +a conflicting manual `mcp_servers.nemo-relay` entry before installation. + +## Route Model Traffic + +Hermes API hooks can provide sanitized model request and response data. Route +provider traffic through Relay when you also need direct gateway observation +of the provider exchange. + +For an OpenAI-compatible provider, point Hermes at the shared Relay gateway: -Create `.nemo-relay/config.toml` for project defaults or -`~/.config/nemo-relay/config.toml` for user defaults: - -```toml -[agents.hermes] -command = "hermes" +```bash +export OPENAI_BASE_URL=http://127.0.0.1:47632/v1 ``` -Then configure observability with `nemo-relay plugins edit --project` or -`.nemo-relay/plugins.toml`: +In your user-level Relay configuration, set the real upstream separately so +Relay does not route requests back to itself: ```toml -version = 1 - -[[components]] -kind = "observability" -enabled = true - -[components.config.atif] -enabled = true -output_directory = ".nemo-relay/atif" - -[components.config.atof] -enabled = true -output_directory = ".nemo-relay/atof" - -[components.config.openinference] -enabled = true -endpoint = "http://127.0.0.1:4318/v1/traces" +[upstream] +openai_base_url = "https://api.openai.com/v1" ``` -Run `nemo-relay run --agent hermes` to use the configured command and plugin -config. User config takes priority over project and system config. +When Hermes uses the Messages API instead, set +`ANTHROPIC_BASE_URL=http://127.0.0.1:47632` and configure Relay's +`upstream.anthropic_base_url` with the real provider address. The installer does +not change Hermes provider settings because they are provider-specific and +user-owned. -## Hermes Hook Setup +## Lifecycle Hook Coverage -Unlike the other agents, Hermes reads hooks from `.hermes/config.yaml`. The -setup wizard writes that file for you when you select hermes — running -`nemo-relay config` (or `nemo-relay config hermes` to scope to one agent) merges -NeMo Relay hook commands into the YAML, preserving any existing config, and -records the path under `[agents.hermes].hooks_path` in `.nemo-relay/config.toml`. +Relay installs these Hermes hooks: -The generated Hermes hooks cover `on_session_start`, `on_session_end`, -`on_session_finalize`, `on_session_reset`, `pre_llm_call`, `post_llm_call`, -`pre_api_request`, `post_api_request`, `api_request_error`, `pre_tool_call`, -`post_tool_call`, `subagent_start`, and `subagent_stop`. +- Session: `on_session_start`, `on_session_end`, `on_session_finalize`, and + `on_session_reset`. +- Model: `pre_api_request`, `post_api_request`, `api_request_error`, + `pre_llm_call`, and `post_llm_call`. +- Tool: `pre_tool_call` and `post_tool_call`. +- Subagent: `subagent_start` and `subagent_stop`. -The API hooks are the main Hermes LLM lifecycle path for NeMo Relay. The legacy -LLM hooks remain installed because they can still provide useful private hints, -but they are not treated as equal peers to the API hooks in the observability -contract. +Relay records Hermes `skill_view` pre-tool calls as observed `skill.load` marks. +Each mark stores the skill name in `data.skill_name` and records the detection +source and tool name in metadata. It does not retain a skill path. -Hermes hook forwarding prefers `NEMO_RELAY_GATEWAY_URL` when set (this is what -`nemo-relay hermes` injects on every run). When launched outside the wrapper — -e.g., bare `hermes` against a long-running gateway — the hook command falls -back to `--gateway-url http://127.0.0.1:4040`. +The API-request hooks are authoritative for hook-backed model lifecycle. +`pre_llm_call` and `post_llm_call` remain private correlation hints. Gateway +routing is a separate observation path. If API-request hooks and gateway +routing are both active for the same provider call, exported data can contain a +hook-observed span and a gateway-observed span for that call. -For standalone gateway mode, start the daemon manually: +## Transparent Run + +Use the wrapper for project-specific Relay configuration or an isolated +per-process gateway: ```bash -nemo-relay --bind 127.0.0.1:4040 +nemo-relay hermes +nemo-relay hermes -- chat --provider custom ``` -Then point Hermes provider traffic at `http://127.0.0.1:4040` for any provider -mode that exposes a local OpenAI-compatible or Anthropic-compatible base URL. -This is the practical routed-provider validation path today. - -Important distinction: - -- Wrapped execution is the authoritative path for Hermes hook-path validation. -- Wrapped execution does not automatically rewrite Hermes provider `base_url`, - `custom_providers`, or `api_mode`. -- Routed `/v1/messages`, `/v1/chat/completions`, or `/v1/responses` validation - therefore requires explicit Hermes provider configuration in addition to the - wrapped or standalone gateway. - -## Smoke Test +This mode starts a gateway on an OS-assigned loopback port and exports +`NEMO_RELAY_GATEWAY_URL` to Hermes. NeMo Relay creates a process-private +`HERMES_HOME` overlay, removes any Relay-managed fixed MCP entry, and injects +temporary hooks. The overlay points Hermes's custom provider at the dynamic +gateway, and the wrapper sets `OPENAI_BASE_URL` to the same address. It links +the rest of the active Hermes profile state instead of copying it. -Use this smoke test to verify the Hermes hook-forward path without launching -Hermes. It sends one synthetic Hermes hook payload through the same -`nemo-relay hook-forward hermes` command that generated hooks use. +The user configuration is never rewritten, so a transparent Hermes process and +a bare Hermes process cannot see each other's temporary hooks. NeMo Relay +removes the overlay after the wrapped process exits. On Windows, profile +directories use non-privileged directory junctions rather than symbolic links, +so this does not require Developer Mode or elevation. -Start a standalone gateway in one terminal: +Inspect the launch without changing files or starting Hermes: ```bash -nemo-relay --bind 127.0.0.1:4040 +nemo-relay run --agent hermes --dry-run --print -- hermes ``` -Then check Hermes hook forwarding from another terminal: +## Remove the Integration + +Remove the Relay-owned Hermes MCP, hooks, trust, and generation state with: ```bash -curl -f http://127.0.0.1:4040/healthz -printf '{"session_id":"smoke-hermes","hook_event_name":"on_session_start"}' \ - | NEMO_RELAY_GATEWAY_URL=http://127.0.0.1:4040 nemo-relay hook-forward hermes --fail-closed +nemo-relay uninstall hermes ``` -The response should be `{}`. If Hermes prompts for hook consent, approve the -NeMo Relay hook command interactively or through Hermes configuration before -relying on unattended capture. +Unrelated Hermes configuration remains unchanged. The interactive +`nemo-relay config hermes` and `nemo-relay config --reset hermes` commands +manage only transparent-run configuration. Use `install` and `uninstall` to +manage the persistent Hermes MCP, hooks, and trust records. -## Verify Export +## Validate End-to-End Capture -End a Hermes turn or finalize the session and confirm the configured exporters -received data: +When a compatible Hermes CLI is installed, run the opt-in cold-start test: ```bash -ls .nemo-relay/atof -ls .nemo-relay/atif +just test-hermes-mcp-e2e ``` -The gateway writes or updates an ATIF snapshot when it receives -`on_session_end`, `on_session_finalize`, or `on_session_reset`. -`on_session_end` is a per-turn snapshot boundary: it does not close the NeMo -Relay session and does not emit a visible trajectory mark. `on_session_finalize` -and `on_session_reset` close the session. When ATOF export is enabled, the raw -event stream is written continuously as lifecycle events arrive. - -## Troubleshoot LLM Lifecycle - -If hook events appear but LLM spans are missing, Hermes model traffic is not -routed through the gateway. If LLM spans exist but attach to the top-level agent -instead of a subagent, include shared identifiers in Hermes hook payloads and -gateway requests, such as `conversation_id`, `generation_id`, `request_id`, or -`x-nemo-relay-subagent-id`. +The test uses isolated home directories and a local model provider. It runs 10 +cold one-shot sessions and two concurrent sessions. It then checks provider +authorization, shared gateway lifetime, exactly-once model and turn scopes, +session isolation, balanced ATOF output, hook trust, and final port release. +This opt-in test is not part of the required Rust CI suite. + +## Troubleshoot + +If Hermes reports that the MCP server failed to start, run +`nemo-relay doctor --plugin hermes` to check the installation and inspect any +reported configuration or endpoint error. If the doctor passes, inspect the +Hermes error for a gateway startup or bind failure. Detached gateways do not +create persistent log files. NeMo Relay rejects a foreign listener on +`127.0.0.1:47632` instead of adopting it. + +If lifecycle events appear but direct model spans do not, check whether the +Hermes provider base URL points at Relay. If model spans attach to the parent +instead of a subagent, preserve Hermes correlation identifiers such as +`task_id`, `turn_id`, `api_request_id`, and `tool_call_id` in the hook payloads. diff --git a/docs/nemo-relay-cli/plugin-installation.mdx b/docs/nemo-relay-cli/plugin-installation.mdx index 920887fee..518858ed8 100644 --- a/docs/nemo-relay-cli/plugin-installation.mdx +++ b/docs/nemo-relay-cli/plugin-installation.mdx @@ -1,42 +1,61 @@ --- -title: "Plugin Installation" -description: "Install and manage NeMo Relay host plugins for Claude Code and Codex." +title: "Coding Agent Installation" +description: "Install and manage persistent NeMo Relay integrations for Claude Code, Codex, and Hermes Agent." position: 3 --- {/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */} -Install a host plugin when you want Claude Code or Codex to load NeMo Relay -through its normal plugin system instead of through a `nemo-relay` wrapper -command. +Install a persistent integration when you want Claude Code, Codex, or Hermes +Agent to load NeMo Relay without a `nemo-relay` wrapper command. Claude Code +and Codex use their normal plugin systems. Hermes uses the same native MCP +lifecycle through its user configuration without a marketplace plugin. + +Each persistent integration forwards the lifecycle signals that its host +exposes, including agent, subagent, tool, prompt, compaction, and stop events +where available. Model-provider routing sends LLM traffic through the local +NeMo Relay gateway. Hooks alone cannot capture complete LLM request and response +spans. -The installed plugin emits agent, subagent, tool, prompt, compaction, and stop -lifecycle signals. Model-provider routing sends LLM traffic through the local -NeMo Relay gateway. Hooks alone cannot capture complete LLM request and -response spans. +All three integrations share the same lifecycle gateway. Provider traffic joins +that gateway when routing is configured for the host: + +```text +Codex / Claude Code / Hermes Agent + |-> Relay MCP lifecycle client -------> shared Relay gateway + |-> generated lifecycle hooks --------> shared Relay gateway + \-> provider route (when configured) -> shared Relay gateway -> model provider +``` + +Each agent starts its own lightweight MCP stdio process. Those processes share +one compatible gateway. Generated lifecycle hooks use `hook-forward` to deliver +their canonical payloads to that same gateway. ## Requirements Install `nemo-relay` and ensure that it is available on `PATH` or `%PATH%`. -The plugin installer does not download a second Relay binary, install a daemon, -or require a plugin-local executable. +The installer does not download a second Relay binary, install a daemon, or +require an integration-local executable. -The selected host CLI must also be available: +The selected coding-agent CLI must also be available at a supported version: -- `claude` for Claude Code plugin installation. -- `codex` for Codex plugin installation. +- Claude Code 2.1.121 or newer through `claude`. +- `codex-cli` 0.143.0 or newer through `codex`. +- Hermes Agent 0.18.2 or newer through `hermes`. -## Install Host Plugin +Relay checks the selected CLI version before modifying agent configuration. +Prerelease, malformed, and older versions are rejected consistently by install, +doctor, and transparent launch paths. -Run the command for the host plugin that you want to install: +## Install a Persistent Integration -```bash -nemo-relay install claude-code -``` +Run the command for the coding-agent integration that you want to install: ```bash +nemo-relay install claude-code nemo-relay install codex +nemo-relay install hermes ``` Install every supported host detected on the machine: @@ -45,18 +64,20 @@ Install every supported host detected on the machine: nemo-relay install all ``` -`install all` selects only hosts whose CLI is present. It fails if neither -Claude Code nor Codex is detected. +`install all` selects only supported agents whose CLI is present. It fails if +Claude Code, Codex, and Hermes Agent are all absent. -Use `--dry-run` to inspect the generated marketplace paths and host commands -without writing files or changing host configuration: +Use `--dry-run` to preview the operation without changing host configuration. +For Claude Code and Codex, the preview includes marketplace paths and host +commands. For Hermes, it shows the user configuration path: ```bash nemo-relay install codex --dry-run ``` -Use `--install-dir` when you need a non-default marketplace location. The -default directory is platform-specific: +For Claude Code and Codex, use `--install-dir` when you need a non-default +marketplace location. Hermes always updates its user configuration. The default +plugin directory is platform-specific: | Platform | Default Plugin Install Directory | | --- | --- | @@ -64,56 +85,200 @@ default directory is platform-specific: | Linux | `${XDG_DATA_HOME:-~/.local/share}/nemo-relay/plugins` | | Windows | `%LOCALAPPDATA%\nemo-relay\plugins` | +Relay serializes install, force-install, rollback, and uninstall operations for +each user and host, even when two operations name different install +directories. If another operation is still active after a short wait, Relay +stops with a timeout instead of changing host-wide plugin state concurrently. + ## What Install Changes -`nemo-relay install` writes a local marketplace named `nemo-relay-local`, then -registers the generated `nemo-relay-plugin` package with the selected host. +For Claude Code and Codex, `nemo-relay install` writes a local marketplace named +`nemo-relay-local`, then registers the generated `nemo-relay-plugin` package +with the selected host. -For Claude Code, install registers the local Claude marketplace, installs -`nemo-relay-plugin@nemo-relay-local` at user scope, and enables provider routing -through the local NeMo Relay sidecar. During installation, NeMo Relay preserves +For Claude Code, `nemo-relay install claude-code` registers the local +marketplace, installs `nemo-relay-plugin@nemo-relay-local` at user scope, and +enables provider routing through the local NeMo Relay sidecar. Relay preserves existing Claude authentication and model settings and backs them up only when -it adds the Relay provider route. - -For Codex, install registers the local Codex marketplace, installs -`nemo-relay-plugin@nemo-relay-local`, enables Codex hooks, merges generated hook -entries, and configures the `nemo-relay-openai` provider alias at -`http://127.0.0.1:47632`. - -Codex plugin mode is hook-supervised on-demand startup only. It does not install a -Codex wrapper, user-level daemon, launch agent, system user service, -scheduled task, login item, or persistent supervisor. The sidecar starts when an -installed Codex hook runs, reuses an already healthy sidecar when one exists, -and exits after its idle timeout. - - -The plugin captures the first provider request only when Codex fires an -installed hook before that request. If a Codex version calls the provider -before any hook, the plugin cannot guarantee first-request capture under -hook-only lazy startup. - +it adds the provider route. The plugin also declares the native +`nemo-relay mcp` lifecycle client. + +For Codex, `nemo-relay install codex` registers the local marketplace, installs +`nemo-relay-plugin@nemo-relay-local`, enables Codex hooks, and configures the +`nemo-relay-openai` provider alias at `http://127.0.0.1:47632`. The plugin's +`hooks/hooks.json` is the sole persistent Relay hook source. The installer uses +the Codex app-server API to select hooks by plugin ID, exact canonical command, +and event definition. It never trusts unrelated user, project, or plugin hooks. +If Codex does not discover exactly one enabled and trusted handler for every +generated event—`SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, +`PermissionRequest`, `SubagentStart`, `SubagentStop`, `Stop`, `PreCompact`, and +`PostCompact`—installation restores the previous Codex config, legacy hooks, +Relay backup files, and every original targeted trust entry. These 10 events are +the complete supported Codex 0.143 plugin hook schema; Relay does not generate +undiscoverable `PostToolUseFailure`, `Notification`, or `SessionEnd` handlers. +Upgrade removes legacy Relay groups from +`~/.codex/hooks.json` while preserving unrelated hooks. + +For Hermes, `nemo-relay install hermes` adds the Relay MCP server, canonical +hooks, exact hook trust, and environment references to +`$HERMES_HOME/config.yaml`, or `~/.hermes/config.yaml` when `HERMES_HOME` is +unset. Relay applies these changes in one transaction and preserves unrelated +Hermes configuration. Refer to the +[Hermes Agent guide](/nemo-relay-cli/hermes). + +On Windows, generated hooks use the built-in Windows PowerShell encoded-command +format. This avoids quoting and metacharacter differences between the Codex, +Claude Code, and Hermes command runners. The encoded payload contains only the +canonical `nemo-relay.exe` path and `hook-forward` arguments. PowerShell starts +that Rust binary directly and preserves its standard input, standard output, +standard error, and exit code. The MCP client and gateway remain Rust-native, +and both install and doctor verify the generated command and event ownership. + +Start a new Codex CLI process after installation. Restart the Codex desktop app +if it was already running so it reloads the provider and hook configuration. + +### Shared Gateway Lifecycle + +Claude Code, Codex, and configured Hermes processes use the same +`nemo-relay mcp` lifecycle client. Before reading MCP protocol frames, it starts +or reuses a detached native sidecar on `127.0.0.1:47632`. The client verifies +Relay identity, version, protocol readiness, and effective user-level +configuration, then heartbeats the sidecar every 30 seconds. Concurrent +processes from any host share the gateway. After the final MCP client closes, +the sidecar exits after 300 idle seconds by default. If the gateway exits while +MCP stdio is open, the client performs one coordinated restart and fails if +recovery does not succeed. Relay reports a port conflict for a foreign process +instead of accepting it merely because it returned HTTP 200. + +Health and readiness responses include a per-process instance ID. A per-user +startup lock serializes launch and recovery, while a small recovery record lets +overlapping MCP clients share the same restart attempt. The gateway tracks its +own activity, idle shutdown, and authenticated ownership-record cleanup. + +Codex marks the MCP server as required, so the captured turn waits for verified +gateway readiness. Claude Code 2.1.121 or newer uses `alwaysLoad`, which blocks +session startup until the MCP connection is ready. Hermes starts MCP discovery +asynchronously, so its first lifecycle hook can arrive before MCP is ready. +Installed MCP entries and `hook-forward` commands carry both the generation-file +path and the immutable identity expected at that path. This prevents a host +process with cached plugin configuration from adopting a replacement +installation after `--force`. Persistent hooks wait for the MCP-owned gateway, +verify it on the same connection used for delivery, and send the canonical +payload once. They never start or recover the gateway, and Relay does not retry +after payload transmission begins. The MCP server advertises no tools in any +host. + +### Hook Delivery and Upgrade Safety + +Before contacting the gateway, Relay rejects a fixed-endpoint `hook-forward` +command that lacks a valid installer-owned generation fence. It uses the +configured hook failure policy. This prevents a legacy hook retained by a host +process from reviving a retired installation. If install or uninstall reports a +missing or invalid generation marker, follow its cleanup instructions: close +the host and standalone `nemo-relay mcp` processes, remove the stale +registration and state it identifies, and then run the requested `--force` +command. + +Transparent hooks embed their process-private gateway URL and never recover a +persistent gateway, so they do not need this fence. Source plugins and custom +automation can use `--forward-only` to contact an existing gateway without a +fence. That mode waits for an authenticated, configuration-compatible Relay +gateway and rejects foreign or incompatible listeners before it sends the +payload. Authentication and delivery use the same TCP connection, so a process +that replaces the listener after a health probe cannot receive lifecycle data. +The mode never launches or recovers Relay. + +Relay cannot guarantee exactly-once delivery if the gateway fails after it +accepts a request. It does not keep a durable hook outbox or deduplication +journal. The opt-in end-to-end tests verify exactly-once lifecycle output for +the healthy and cold-start paths they exercise, not for arbitrary crashes +between request acceptance and response delivery. + +On Windows, Relay requests Job Object breakaway when the host job permits it. +When breakaway is unavailable and the host permits nested assignment, the +gateway remains scoped to the host job and can exit before the normal idle reuse +window. If the host rejects nested assignment, persistent bootstrap stops and +explains the conflict instead of running without process-tree cleanup +guarantees. + +The compatibility fingerprint covers resolved provider, exporter, dynamic +plugin, and relevant environment settings without exposing their values in the +health response. For Claude Code and Codex, +`nemo-relay install --force` rotates the immutable MCP and hook generation +identity, then uses a private ownership token to stop the shared fixed-endpoint +gateway as one serialized operation. Ownership is keyed by endpoint rather than +host, so an upgrade from any installed host can retire a gateway that another +host originally started. + +Relay leaves custom managed endpoints untouched and never sends its shutdown +token to an unrelated listener. Hermes installation rotates its MCP generation +fence in the same transaction. + +### Configuration and Credentials + +Persistent mode resolves only system and user Relay configuration and starts in +the user configuration directory. Project `.nemo-relay` layers remain available +through transparent `nemo-relay run` invocations. Each host passes environment +settings to MCP differently: + +- Codex stores variable names—not values—for provider credentials, Relay + runtime, OpenTelemetry, AWS, proxies, certificates, approved prefixes, and + credential variables referenced by user observability configuration. +- Claude Code supplies its normal plugin MCP environment. +- Hermes stores `${VARIABLE_NAME}` references for the standard allowlist, + custom variables that you have already approved, and credential names + referenced by user observability configuration. + +Relay treats unresolved self-references as unset before parsing its command +line. Rerun `nemo-relay install hermes --force` after you add a custom approved +name or change a credential name referenced by the configuration. + +The long-lived managed sidecar injects a forwarded provider credential only when +a request carries provider authorization or Relay's private per-user client +proof. The Codex installer writes a domain-separated HMAC proof into the +managed provider's `http_headers`; Relay stores the resulting Codex config with +an owner-only mode on Unix or protected owner/System DACL on Windows before the +temporary file becomes visible. Relay validates and removes that header before +middleware, observability, or upstream forwarding. The underlying HMAC key +remains in Relay's owner-only bootstrap state. Claude Code and Hermes send +their normal provider authorization. A foreign loopback caller cannot spend +the sidecar's forwarded credentials. Foreground `nemo-relay --bind` use retains +environment-key injection for explicit local proxy workflows. ## Diagnose -Run the command for the installed host that you want to diagnose: +Run the command for the installed integration that you want to diagnose: ```bash nemo-relay doctor --plugin claude-code -``` - -```bash nemo-relay doctor --plugin codex -``` - -```bash +nemo-relay doctor --plugin hermes nemo-relay doctor --plugin all ``` -`nemo-relay doctor` includes every persistent host-plugin installation found in -the default platform install directory. It checks the generated marketplace and -plugin files, Relay binary and hook support, host registration, provider -routing, hooks, and lazy-sidecar assumptions. A stopped Codex lazy sidecar is -informational; hooks start it on first use. +`nemo-relay doctor` includes every persistent integration it finds. It checks +generated marketplace and plugin files where applicable, the Relay binary and +hook support, agent version, registration, provider routing, hooks, and sidecar +readiness assumptions. For Codex, doctor also asks the Codex app-server whether +each exact Relay-generated hook is trusted and enabled. JSON output includes +`checks.codex_hooks_trusted` and a +`codex_hook_trust` object that groups trusted, untrusted, modified, disabled, +missing, or duplicated required hooks. A stopped Codex sidecar is informational; +the required plugin MCP starts it before the captured turn. Doctor also reports +newly required forwarded environment names and recommends +`nemo-relay install codex --force`. + +For Claude Code, doctor also validates version 2.1.121 or newer, the generated +`alwaysLoad` MCP server, and its generation marker. A stopped sidecar remains +informational because the next MCP process starts it. Persistent hooks wait for +that MCP-owned gateway instead of starting it themselves. + +For Hermes, doctor validates Hermes Agent 0.18.2 or newer. It reads the Relay +executable from the managed Hermes MCP entry, verifies that exact executable +supports both `mcp` and `hook-forward`, and then checks every hook and trust +pair, the generation marker, and environment references. Top-level +`nemo-relay doctor` discovers this managed state directly from the Hermes user +configuration; an `[agents.hermes]` Relay configuration block is not required. Use the focused plugin doctor when diagnosing one host or an installation that uses a custom directory: @@ -122,31 +287,76 @@ uses a custom directory: nemo-relay doctor --plugin codex --install-dir /path/to/plugins ``` -If an installed host plugin is incomplete, doctor reports the failed check and -suggests `nemo-relay install --force`. Hosts without a persistent plugin -installation remain informational, so transparent-run setup does not require a -host plugin. +If an installed integration is incomplete, doctor reports the failed check and +suggests `nemo-relay install --force`. Agents without a persistent +installation remain informational, so transparent-run setup does not require +one. ## Uninstall -Run the command for the installed host plugin that you want to remove: +Run the command for the installed integration that you want to remove: ```bash nemo-relay uninstall claude-code -``` - -```bash nemo-relay uninstall codex -``` - -```bash +nemo-relay uninstall hermes nemo-relay uninstall all ``` -Uninstall removes the generated host plugin registration and marketplace entry, -restores Claude Code provider routing from the Relay backup, and removes -generated Codex hook/provider configuration, while preserving unrelated user -configuration. +Uninstall removes Codex provider configuration and trust for the exact plugin +hooks before unregistering the plugin, while Codex can still report its hook +metadata. It then removes the host registration and marketplace. Claude Code +provider routing is restored from the Relay backup. Unrelated user hooks and +configuration remain unchanged. Hermes uninstall removes only Relay-owned MCP, +hook, trust, and generation state. + +## Compatibility and Migration + +These integrations now require Codex 0.143.0, Claude Code 2.1.121, or Hermes +Agent 0.18.2 at minimum. Relay rejects prerelease or malformed version output. +If you configured an opaque wrapper, its `--version` output must identify the +selected host. Interactive `nemo-relay config hermes` now owns only +transparent-wrapper configuration; use `nemo-relay install hermes` and +`nemo-relay uninstall hermes` for persistent Hermes state. + +The MCP bootstrap is now host-neutral. Generated Codex, Claude Code, and Hermes +configuration invokes `nemo-relay mcp`; the removed `--agent` option no longer +parses. Existing fenced installations can be refreshed with +`nemo-relay install --force`. Relay refuses to replace an older MCP +installation without a valid generation marker because a cached host process +might still be running. In that case, follow the manual cleanup steps in the +error before you retry the forced install. + +This release removes the internal `nemo-relay plugin-shim` command. Refresh a +fenced generated installation with `nemo-relay install --force`; use the +manual cleanup described above when its generation marker is missing. For +custom automation, use these supported replacements: + +| Removed Internal Command | Supported Replacement | +| --- | --- | +| `nemo-relay plugin-shim hook ` | `nemo-relay hook-forward --forward-only` with the canonical lifecycle payload on standard input and an existing gateway | +| `nemo-relay plugin-shim serve` | `nemo-relay mcp` for host-managed lifecycle, or `nemo-relay --bind
` to run a gateway directly | +| `nemo-relay plugin-shim install ` | `nemo-relay install ` | +| `nemo-relay plugin-shim uninstall ` | `nemo-relay uninstall ` | +| `nemo-relay plugin-shim provider claude enable` | `nemo-relay install claude-code` | +| `nemo-relay plugin-shim provider claude restore` | `nemo-relay uninstall claude-code` | +| `nemo-relay plugin-shim provider claude status` | `nemo-relay doctor --plugin claude-code` | +| `nemo-relay plugin-shim doctor ` | `nemo-relay doctor --plugin ` | + +Persistent generated hook commands include the fixed gateway URL. Transparent +wrapper hooks embed their dynamic gateway URL, while the process environment +lets an installed plugin MCP authenticate, borrow, and monitor that exact +gateway. Transparent hook delivery authenticates the wrapper gateway before +writing its lifecycle payload. When a transparent run uses a recognizable +wrapper command, such as `npx codex`, Relay checks the host version through that +wrapper. Opaque custom wrappers remain supported when the configured command +emits the selected host's canonical version output in response to `--version`. +Install and doctor enforce the same minimum-version policy. + +Hermes setup uses the same public install, doctor, and uninstall commands as +Claude Code and Codex. `nemo-relay config hermes` configures Relay's transparent +wrapper only; it no longer mutates Hermes user configuration. Use +`nemo-relay install hermes` for the persistent host integration. ## Source Marketplace Discovery @@ -157,8 +367,12 @@ validation: - `.agents/plugins/marketplace.json` Those manifests are useful when validating host plugin metadata from a source -checkout. For end-user setup, use `nemo-relay install `. It generates the -local marketplace, registers the host plugin, and performs the required -provider and hook setup. Avoid keeping both a source-installed -plugin and a generated install active for the same host because both can forward -the same hook payload. +checkout. For end-user setup, use `nemo-relay install `. For Claude Code +and Codex, the command generates the local marketplace, registers the host +plugin, and performs the required provider and hook setup. Source manifests do +not provide Codex's complete provider, environment-forwarding, and +verified-trust workflow. + +Do not keep a source-installed plugin and the generated plugin active for the +same host. Both can forward the same lifecycle payload and produce duplicate +events. Remove the source-installed plugin before you use the generated install. diff --git a/integrations/coding-agents/README.md b/integrations/coding-agents/README.md index ba11361cd..3f6bea221 100644 --- a/integrations/coding-agents/README.md +++ b/integrations/coding-agents/README.md @@ -22,18 +22,24 @@ environment variables, or shared TOML config. ## Packages +Each host uses a slightly different integration surface: + - `claude-code/` is a Claude Code plugin package. The - `nemo-relay install claude-code` command installs hook entries targeting - `POST /hooks/claude-code` through `nemo-relay` on `PATH`. + `nemo-relay install claude-code` command installs a native MCP lifecycle + client and hook entries targeting `POST /hooks/claude-code` through + `nemo-relay` on `PATH`. - `codex/` is a Codex plugin package. `nemo-relay install codex` creates the marketplace, installs the plugin, enables `features.hooks = true`, and configures a local `nemo-relay-openai` provider alias. Codex plugin delivery - uses hook-supervised lazy sidecar startup only, with no wrapper, user-level daemon, - login item, launchd agent, systemd user service, scheduled task, or persistent - supervisor. -- Hermes does not require a static bundle in this directory. The setup wizard - (`nemo-relay config`) merges hook commands into `.hermes/config.yaml` when - hermes is selected. + uses required native `nemo-relay mcp` lifecycle clients. Claude Code starts + the same lifecycle client automatically from its plugin. Clients from either + host share one Rust gateway, subject to the Windows Job Object lifetime + caveat below, with no wrapper, login item, launchd agent, systemd user + service, scheduled task, or persistent supervisor. +- Hermes does not require a static marketplace bundle. The + `nemo-relay install hermes` command adds a native MCP lifecycle client, + canonical hooks, and exact per-event trust to the user-owned Hermes config as + one transaction. ## Transparent Setup @@ -49,9 +55,10 @@ nemo-relay run -- codex nemo-relay run -- hermes ``` -Use `--agent claude|codex|hermes` when a wrapper hides the agent -command name. Use `--dry-run --print` to inspect generated config without -launching. +When a wrapper hides the agent command name, configure that wrapper under +`[agents.].command` and select it with +`--agent claude|codex|hermes`. Use `--dry-run --print` to inspect generated +config without launching. Use `nemo-relay doctor` to inspect environment, config, agent commands, hook readiness, observability outputs, and shell completions. Scope the report to one @@ -66,34 +73,77 @@ nemo-relay doctor hermes --json The command is read-only: it reports missing ATIF directories, hook files, and agent commands instead of creating or patching them. -## Plugin Installation - -The Claude Code and Codex plugins are installed by the `nemo-relay` CLI. The -CLI must already be installed and discoverable on `$PATH` or `%PATH%`; no -separate npm installer, release bundle download, or plugin-local Relay binary is -required. - -Claude Code can start the sidecar from plugin hooks or helper commands and route -model traffic by setting `ANTHROPIC_BASE_URL` to the sidecar URL. - -Codex does not use a daemon in plugin mode. The installed Codex hooks call the -`nemo-relay plugin-shim hook codex` command. The shim then checks `/healthz`, starts -the local `nemo-relay` sidecar, if needed, waits briefly for readiness, and then -forwards the hook payload. Codex model traffic is routed through the stable -provider alias at `http://127.0.0.1:47632`. - -Install the local host marketplaces with: +## Persistent Integration Installation + +The `nemo-relay` CLI installs the Claude Code and Codex plugins and manages the +Hermes user integration. The CLI must already be available on `$PATH` or +`%PATH%`; you do not need a separate npm installer, release bundle, or +plugin-local Relay binary. + +Persistent installation and transparent launch require Claude Code 2.1.121 or +newer, `codex-cli` 0.143.0 or newer, or Hermes Agent 0.18.2 or newer for the +selected agent. + +Each plugin MCP entry—and the equivalent Hermes `mcp_servers` entry—starts +`nemo-relay mcp`, a lightweight client that starts or reuses a native +`nemo-relay --bind 127.0.0.1:47632` sidecar. Relay detaches the sidecar when +host policy permits. A restrictive Windows Job Object can limit the sidecar to +the host job. If nested assignment cannot provide the required process-tree +cleanup guarantee, bootstrap stops and explains the conflict. + +The MCP process acquires the gateway before reading protocol frames and returns +its initialization response only after it verifies Relay identity, version, +and bootstrap-protocol readiness. Concurrent Codex, Claude Code, and +Hermes processes share the gateway and heartbeat it while their MCP stdio +connections remain open; the gateway exits after the final client's idle +timeout. Overlapping MCP clients coordinate one restart for the endpoint, even +when their heartbeats arrive at different times. +Codex requires MCP initialization before the captured turn. Claude Code marks +Relay MCP as `alwaysLoad`, so it also waits for the connection before session +startup. Hermes starts MCP discovery asynchronously, so an early +generation-fenced command hook waits for the MCP-owned gateway. Installed MCP +entries and hook commands carry both their generation-file path and the +immutable identity expected there, so cached host configuration cannot adopt a +replacement installation at the same path. The +MCP client advertises no tools. + +MCP bootstrap is host-neutral: all three generated integrations use +`nemo-relay mcp`. Agent identity appears only in lifecycle hook commands, where +Relay needs it to translate each host's canonical payload. The old +`mcp --agent ` form no longer parses. Refresh a fenced installation with +`nemo-relay install --force`; if its generation marker is missing, Relay +refuses the upgrade and prints the manual cleanup steps. + +Persistent mode loads system and user Relay configuration only and starts the +sidecar from the user configuration directory. This keeps relative exporter +paths stable across projects. Codex's generated MCP manifest forwards +approved provider, Relay, OpenTelemetry, AWS, proxy, certificate, and +config-referenced credential environment names without storing their values; +Claude Code supplies its normal MCP process environment. Use transparent +`nemo-relay run` for project-specific configuration. The managed sidecar +injects a forwarded provider key only for a request with provider authorization +or Relay's private per-user client proof. Codex receives that derived proof in +its managed provider headers; the installer writes that config privately and +Relay consumes the proof before middleware, telemetry, or upstream forwarding. +Claude Code and Hermes send their normal provider authorization, so an +unrelated loopback caller cannot spend forwarded keys. + +Install the persistent integrations with: ```bash nemo-relay install claude-code nemo-relay install codex +nemo-relay install hermes nemo-relay install all ``` -`nemo-relay install` writes local marketplace files, registers the selected host -plugin, and performs the required host provider and hook setup. Use -`nemo-relay uninstall ` to roll back and `nemo-relay doctor --plugin -` to check an installed plugin. +For Claude Code and Codex, `nemo-relay install` writes local marketplace files, +registers the selected host plugin, and performs the required provider and hook +setup. For Hermes, `install` is the only command that updates Relay-owned user +MCP, hook, trust, and generation state; interactive `config hermes` manages +only the transparent wrapper. Use +`nemo-relay uninstall ` to roll back and +`nemo-relay doctor --plugin ` to check an installed integration. If you are using Codex, add this repository as a marketplace for source/dev discovery: @@ -103,12 +153,20 @@ codex plugin marketplace add NVIDIA/NeMo-Relay codex plugin add nemo-relay-plugin@nemo-relay ``` -That path relies on `nemo-relay` being available on `PATH`; source plugin hooks -invoke `nemo-relay plugin-shim hook codex` directly. +That path relies on `nemo-relay` being available on `PATH`. Source plugin hooks +use `nemo-relay hook-forward codex --forward-only`: they post to the gateway +started by the required MCP entry but cannot launch or recover Relay without an +installer-owned generation fence. Before posting, they authenticate the Relay +identity and verify that its user-level configuration matches. The proof and +payload use one TCP connection, preventing a replacement listener from +receiving the payload after verification. -Use the source marketplace path for discovery or manifest validation. Remove -the source-installed Codex plugin before running `nemo-relay install codex`; -keeping both active can forward the same Codex hook twice. +Use the source marketplace path for discovery or manifest validation. Use +`nemo-relay install codex` for complete provider routing, environment +forwarding, and verified plugin-hook trust. + +Remove the source-installed Codex plugin before you use the generated install. +If both remain active and trusted, they can forward the same lifecycle payload. Claude Code users can add this repository as a marketplace the same way: @@ -119,19 +177,32 @@ claude plugin install nemo-relay-plugin@nemo-relay --scope user ``` That path reads `.claude-plugin/marketplace.json` from the repository. Source -plugin hooks invoke `nemo-relay plugin-shim hook claude` directly. Use -`nemo-relay install claude-code` for the complete provider-routing setup. +plugin hooks use `nemo-relay hook-forward claude --forward-only`: they post to +the gateway started by the `alwaysLoad` MCP entry but cannot launch or recover +Relay without an installer-owned generation fence. They authenticate that +gateway on the same connection used to send lifecycle data. Use +`nemo-relay install claude-code` for the complete provider-routing setup, and +remove the source-installed plugin first to avoid duplicate lifecycle events. + +Hermes persistent installation is user-level: + +```bash +nemo-relay install hermes +``` -Hermes transparent runs export the dynamic `NEMO_RELAY_GATEWAY_URL`, but Hermes -hooks must already be present in `.hermes/config.yaml` before they can call the -gateway. The setup wizard (`nemo-relay config`) writes that file for you when -you select hermes. +It writes the MCP server and trusted hooks to `$HERMES_HOME/config.yaml` or +`~/.hermes/config.yaml`. Transparent Hermes runs leave that file untouched and +export the dynamic `NEMO_RELAY_GATEWAY_URL` through a process-private +`HERMES_HOME` overlay with no fixed MCP entry. Shared TOML config is loaded from `/etc/nemo-relay/config.toml`, then nearest project `.nemo-relay/config.toml`, then `$XDG_CONFIG_HOME/nemo-relay/config.toml` or `~/.config/nemo-relay/config.toml`. +That layering applies to transparent runs. Persistent mode skips the +project layer and merges only system and user configuration. + ```toml [agents.codex] command = "codex" @@ -160,43 +231,55 @@ enabled = true endpoint = "http://127.0.0.1:4318/v1/traces" ``` -During setup or launch, invalid shared TOML, malformed plugin config, unsupported exporter settings, -or unavailable exporter features will fail closed. The -wrapper does not start the coding agent against a configuration that cannot be -parsed, validated, or activated. Once the gateway and agent are running, +During setup or launch, Relay fails closed on invalid shared TOML, malformed +plugin config, unsupported exporter settings, or unavailable exporter features. +The wrapper does not start the coding agent with a configuration that it cannot +parse, validate, or activate. After the gateway and agent are running, exporter delivery failures follow the observability plugin policy: application work continues while the failing ATOF, ATIF, OpenTelemetry, or OpenInference destination records, logs, or reports the failure. ## Hook Forwarding -The transparent wrapper hooks call `nemo-relay hook-forward ` with the -canonical hook payload on stdin. The wrapper injects `NEMO_RELAY_GATEWAY_URL` so -the same hook command reaches the ephemeral per-run gateway; hermes hooks fall -back to an embedded `--gateway-url` when running outside the wrapper. +Transparent Claude Code and Codex hooks call +`nemo-relay hook-forward ` with the canonical hook payload on standard +input. The wrapper-owned command embeds the ephemeral per-run gateway URL and +is marked as transparent so it never starts or recovers the fixed gateway. + +Persistent Claude Code, Codex, and Hermes hooks call +`nemo-relay hook-forward ` with the fixed gateway and an +installer-owned generation fence. They wait for and authenticate the +MCP-owned gateway, then send the payload once. They never start or recover the +gateway. Transparent Hermes hooks instead embed the wrapper's dynamic gateway +URL. -Claude Code and Codex plugin hooks call `nemo-relay plugin-shim hook `. -The plugin shim ensures the local sidecar is reachable, then forwards the hook -payload to the plugin sidecar endpoint. +For Codex, the installed plugin file is the sole persistent Relay hook source; +installation does not add Relay groups to `~/.codex/hooks.json`. Since hook forwarding fails open by default, gateway or sidecar outages do not block the coding agent. The hook command exits successfully after logging the forwarding problem, so the host agent can continue even though that hook -payload may be missing from telemetry. For wrapper-generated `hook-forward` -commands, add -`--fail-closed` when policy requires hook delivery to block the agent. For -plugin shim hooks, set `NEMO_RELAY_FAIL_CLOSED=1` in the hook execution -environment. In that mode, forwarding failures return a non-zero hook command -status to the host. - -Useful wrapper options: - +payload can be missing from telemetry. For wrapper-generated `hook-forward` +commands, add `--fail-closed` when policy requires hook delivery to block the +agent. For generated persistent hooks, set `NEMO_RELAY_FAIL_CLOSED=1` in the hook +execution environment. In that mode, forwarding failures return a non-zero +hook command status to the host. + +These `hook-forward` options control delivery and metadata: + +- `--gateway-url ` selects the Relay gateway that receives the payload. +- `--forward-only` allows a source plugin or custom automation to use an + existing compatible gateway without an installer-owned generation fence. It + verifies the gateway but never launches or recovers Relay. Generated + installed hooks use a private generation fence instead. - `--session-metadata ''` adds structured metadata to the agent begin event. For example, `--session-metadata '{"user_id":"alice"}'` exposes the string as `user.id` on OTLP trace roots. - `--profile ` records a configuration profile in session metadata. - `--gateway-mode hook-only|passthrough|required` records the expected gateway behavior in session metadata. +- `--fail-closed` returns a failure when delivery fails or Relay rejects the + hook instead of allowing the coding agent to continue. ## LLM Gateway @@ -219,12 +302,28 @@ agent's provider base URL at the gateway manually. ## Verify Export -Run a coding-agent session that starts, uses one tool, and ends. Then confirm -that ATIF was written: +Complete a coding-agent turn or session that uses one tool. Then confirm that +ATIF was written: ```bash ls .nemo-relay/atif ``` -The gateway writes `.atif.json` when it receives a session-end hook -for a session with ATIF configured. +The snapshot boundary depends on the host. Claude Code writes ATIF on +`SessionEnd`. Codex writes a cumulative snapshot on each `Stop` because its +plugin schema does not expose `SessionEnd`. Hermes writes or updates the +snapshot on `on_session_end`, `on_session_finalize`, or `on_session_reset`. + +Run the opt-in host E2E targets when the corresponding CLI is installed. These +targets are intentionally outside `test-rust` and mandatory CI: + +```bash +just test-claude-plugin-e2e +just test-codex-plugin-e2e +just test-hermes-mcp-e2e +``` + +Each target uses an isolated home directory and local mock provider. The Claude +and Hermes targets each run 10 cold sessions plus two concurrent sessions and +verify MCP connection, hook delivery, provider routing, session isolation, +balanced ATOF output, and final port release. diff --git a/integrations/coding-agents/claude-code/README.md b/integrations/coding-agents/claude-code/README.md index d293a27c6..2ba1e60fa 100644 --- a/integrations/coding-agents/claude-code/README.md +++ b/integrations/coding-agents/claude-code/README.md @@ -5,8 +5,9 @@ SPDX-License-Identifier: Apache-2.0 # NeMo Relay Plugin -This package contains Claude Code hook entries that forward canonical Claude -Code hook JSON to `nemo-relay` at `/hooks/claude-code`. +This package contains a native MCP lifecycle client and Claude Code hook entries +that forward canonical Claude Code hook JSON to `nemo-relay` at +`/hooks/claude-code`. Claude Code is the supported Claude integration target. Claude application, Claude web, and Claude desktop sessions are unsupported unless they expose the @@ -14,9 +15,12 @@ same local hook and gateway controls as Claude Code. ## Files +The package contains these files: + - `.claude-plugin/plugin.json` describes the Claude Code hook package. +- `.mcp.json` starts the native `nemo-relay mcp` lifecycle client. - `hooks/hooks.json` contains hook entries that run - `nemo-relay plugin-shim hook claude`. + `nemo-relay hook-forward claude --forward-only`. ## Captured Events @@ -26,7 +30,7 @@ The bundle forwards `SessionStart`, `SessionEnd`, `UserPromptSubmit`, `PreCompact`, and `PostCompact` as scope, tool, mark, or private LLM correlation events. -The bundle requires Claude Code 2.1.121 or newer. These versions provides the +The bundle requires Claude Code 2.1.121 or newer. That version provides the `alwaysLoad` MCP startup barrier used to make Relay ready before session hooks and accepts the complete generated hook schema. Installation fails before changing host state when the version is too old, and `nemo-relay doctor` @@ -51,9 +55,15 @@ nemo-relay run -- claude ``` The wrapper starts a per-invocation gateway on a dynamic localhost port, -creates a temporary Claude plugin directory, passes it with `--plugin-dir`, sets -`ANTHROPIC_BASE_URL` for the launched process, and removes the temporary plugin -when Claude exits. +creates a temporary Claude plugin directory, and passes it with `--plugin-dir`. +It also creates a private settings overlay that preserves the caller's first +explicit `--settings` object and overrides only `ANTHROPIC_BASE_URL` for the +launched process. Relay removes both temporary artifacts when Claude exits and +does not rewrite the source settings or user-level plugin state. An enabled +Relay plugin MCP authenticates, borrows, and monitors the wrapper-owned dynamic +gateway, and its persistent hooks become no-ops for this process; only the +temporary hook source delivers lifecycle data after authenticating that +gateway. Inspect the launch without starting Claude Code: @@ -117,7 +127,7 @@ wrapper, which injects ephemeral hooks per run. ## Verify -Run a Claude Code session that starts, uses one simple tool, and ends. Confirm +Run a Claude Code session that starts, uses one tool, and ends. Confirm that ATIF was written: ```bash @@ -159,6 +169,7 @@ unknown subagent end arrives during an active turn, Relay may emit a Hook events are only available when Claude Code loads this plugin. A standalone gateway observes Anthropic LLM traffic, but it cannot recover missing prompt, tool, compaction, notification, or subagent hooks. + ## Standalone Plugin Installation Preferred release install: @@ -169,7 +180,17 @@ nemo-relay install claude-code `nemo-relay install claude-code` writes a local Claude Code marketplace, installs `nemo-relay-plugin` at user scope, and enables Claude Code provider -routing through NeMo Relay. +routing through NeMo Relay. Its plugin MCP process immediately starts or reuses +the shared native gateway on `127.0.0.1:47632` and heartbeats it while MCP stdio +remains open. Codex, Claude Code, and configured Hermes MCP clients can share +that gateway. + +The generated MCP entry sets `alwaysLoad: true`, so Claude Code waits +for the MCP connection during startup, while `nemo-relay mcp` starts or reuses +the gateway immediately when its process launches. The command hook waits for +and authenticates that MCP-owned gateway but never starts or recovers it. The +MCP server advertises no tools and does not add tool definitions to Claude's +context. No separate provider-routing command is required when installing through `nemo-relay install`. @@ -182,10 +203,14 @@ Package or unpack the plugin so the plugin root contains: ```text nemo-relay-plugin/ .claude-plugin/plugin.json + .mcp.json hooks/hooks.json ``` -The hook shim starts the sidecar lazily if no gateway is already reachable. +Persistent mode uses system and user Relay configuration only and starts the +gateway in the user configuration directory. Use +`nemo-relay run --agent claude` when project-specific Relay configuration is +required. Repo marketplace discovery is also supported: @@ -197,8 +222,12 @@ claude plugin install nemo-relay-plugin@nemo-relay --scope user That path reads `.claude-plugin/marketplace.json` from the repository and installs this Claude Code plugin from `integrations/coding-agents/claude-code`. -Source hooks invoke `nemo-relay plugin-shim hook claude` directly. Use -`nemo-relay install claude-code` for the complete provider-routing setup. +The source plugin starts `nemo-relay mcp`; its hooks use the explicit +`--forward-only` mode to post to that existing gateway without an +installer-owned generation fence. They cannot launch or recover Relay. Use +`nemo-relay install claude-code` for complete provider routing and +generation-fenced upgrades. Remove the source-installed plugin first; keeping +both plugins active can forward the same lifecycle payload twice. Create a local Claude Code marketplace and copy the plugin under that marketplace root: @@ -225,7 +254,7 @@ Create `$MARKETPLACE_ROOT/.claude-plugin/marketplace.json`: "plugins": [ { "name": "nemo-relay-plugin", - "description": "Forward Claude Code lifecycle hooks to a local NeMo Relay sidecar.", + "description": "Run the shared native Relay gateway and capture Claude Code lifecycle events.", "source": "./plugins/nemo-relay-plugin", "category": "development" } @@ -249,8 +278,9 @@ claude --plugin-dir "$PLUGIN_ROOT" ``` Hook commands in the source `hooks/hooks.json` template use -`nemo-relay plugin-shim hook claude`, so source marketplace installs rely on -the same `nemo-relay` executable available on `PATH`. +`nemo-relay hook-forward claude --forward-only`, so source marketplace installs +rely on the same `nemo-relay` executable available on `PATH` and on their +`alwaysLoad` MCP entry for gateway startup. If you set up the marketplace manually for development, use the top-level installer commands for provider routing and rollback: @@ -272,8 +302,11 @@ Start a normal Claude Code session: claude ``` -The installed hooks start the Relay sidecar lazily, and provider traffic is -routed through `ANTHROPIC_BASE_URL=http://127.0.0.1:47632`. +The installed MCP client starts the Relay sidecar before Claude Code proceeds +with session startup. If the sidecar becomes unhealthy, overlapping MCP clients +coordinate one recovery attempt. Hook forwarding waits for that recovery but +does not initiate it. Provider traffic is routed through +`ANTHROPIC_BASE_URL=http://127.0.0.1:47632`. To upgrade manually, replace the plugin directory contents with the new package, keep the same `MARKETPLACE_ROOT`, update the marketplace, and rerun the @@ -282,7 +315,7 @@ top-level installer: ```bash claude plugin marketplace update nemo-relay-local claude plugin update nemo-relay-plugin -nemo-relay install claude-code +nemo-relay install claude-code --force ``` To uninstall, restore Claude Code provider settings, uninstall the plugin, remove diff --git a/integrations/coding-agents/codex/README.md b/integrations/coding-agents/codex/README.md index 028608207..ce88420fc 100644 --- a/integrations/coding-agents/codex/README.md +++ b/integrations/coding-agents/codex/README.md @@ -13,40 +13,123 @@ supported only when they run locally and honor the same hook/plugin config and provider routing. Cloud or remote Codex tasks are partial or unsupported for local gateway LLM capture. -Requires `codex-cli >= 0.129.0` (introduced the `features.hooks` flag and the -provider alias surface the gateway relies on). +Install `codex-cli` 0.143.0 or newer. This version includes the lifecycle-hook +set, plugin app-server metadata, `features.hooks`, and provider alias surfaces +that the installer requires. ## Files +The package contains these files: + - `.codex-plugin/plugin.json` describes the Codex plugin package. +- `.mcp.json` starts the native `nemo-relay mcp` lifecycle client and requires + successful gateway initialization. - `hooks/hooks.json` contains Codex hook entries that run - `nemo-relay plugin-shim hook codex`. + `nemo-relay hook-forward codex --forward-only`. - `nemo-relay install codex` creates the local marketplace, installs the plugin, - and persists Codex hook and provider configuration using `nemo-relay` from - `PATH`. + and persists Codex provider and exact plugin-hook trust using `nemo-relay` + from `PATH`. ## Captured Events -With `codex-cli >= 0.129.0`, the minimum supported installed hooks are -`SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, -`PermissionRequest`, `Stop`, `PreCompact`, and `PostCompact`. +With `codex-cli >= 0.143.0`, persistent installation requires `SessionStart`, +`UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `PermissionRequest`, +`SubagentStart`, `SubagentStop`, `Stop`, `PreCompact`, and `PostCompact`. Relay +requires exactly one enabled and trusted app-server handler for every generated +event and forwards delivered hooks as scope, tool, mark, or private LLM +correlation events. `PostToolUseFailure`, `Notification`, and `SessionEnd` are not +in the Codex 0.143 plugin hook schema and are not generated. + +Each delivered `Stop` closes the active turn and writes a cumulative ATIF +snapshot. Because the plugin schema does not expose `SessionEnd`, the final +`Stop` is the final session snapshot. + +Transparent setup injects hooks with CLI config overrides. Persistent setup +writes `features.hooks = true` in `.codex/config.toml`, configures the +`nemo-relay-openai` provider alias, and uses this plugin's `hooks/hooks.json` as +the sole persistent Relay hook source. It does not add Relay groups to +`.codex/hooks.json`. -The hook template also documents events used by newer or broader host hook -surfaces, including `SessionEnd`, `PostToolUseFailure`, `SubagentStart`, -`SubagentStop`, and `Notification`. Relay forwards any delivered supported hook -as scope, tool, mark, or private LLM correlation events, but the v1 plugin -manifest does not depend on Codex exposing those broader events. +Persistent installation opens the stable Codex app-server interface and +selects only hooks whose source is `plugin`, plugin ID is +`nemo-relay-plugin@nemo-relay-local`, and command exactly matches the generated +canonical Relay forwarding command. It requires exactly one handler for each +event in the complete 10-event supported set listed above. + +Unrelated user, project, and plugin hooks are never trusted. If installation +fails after a trust write, it restores every targeted hook's prior trusted, +modified, disabled, or absent state together with Codex config and backups. +Upgrade removes legacy Relay user-hook groups while preserving unrelated hooks. + +Codex plugin mode starts the native `nemo-relay mcp` subcommand through the +plugin MCP configuration. That Rust process is a lightweight lifecycle client: +it starts or reuses a detached native `nemo-relay --bind 127.0.0.1:47632` +sidecar, rejects foreign listeners, and completes the MCP handshake only after +Relay identity, version, bootstrap protocol, and effective persistent +configuration are verified. Compatibility uses a one-way fingerprint of the +resolved settings and relevant environment values without exposing secrets. +Relay limits the complete dynamic plugin activation snapshot—including adjacent +runtime files and a copied managed Python environment—to 100,000 filesystem +entries, 512 MiB, and a maximum directory traversal depth of 128. If startup +reports an activation snapshot budget error, remove unrelated files from the +manifest or load-target directory, flatten deeply nested directories, or reduce +the managed Python environment before retrying. Concurrent Codex, +Claude Code, and configured Hermes processes can share the gateway and +heartbeat it every 30 seconds. The sidecar remains available for 300 idle +seconds after the final client closes. If it dies while MCP remains open, +overlapping MCP clients coordinate one restart for the endpoint. Persistent +hooks wait for the MCP-owned gateway but do not initiate recovery. The MCP +server advertises no tools. + +On Windows, Relay requests Job Object breakaway only when the host job permits +it. Under a restrictive Job Object that permits nested jobs, Relay keeps the +sidecar scoped to the host job and retains a nested cleanup job for the sidecar +process tree. The sidecar cannot outlive the host job, so the 300-second idle +reuse window can end early. If Relay cannot create or configure the cleanup job, +or the host rejects nested assignment, persistent bootstrap stops and explains +the conflict instead of running without process-tree cleanup guarantees. + +Persistent mode loads only system and user Relay configuration and starts from +the user configuration directory. Project `.nemo-relay` layers remain specific +to transparent `nemo-relay run` invocations. The MCP manifest forwards approved +provider, Relay, OpenTelemetry, AWS, proxy, certificate, and config-referenced +credential variable names without storing values. + +The installer also derives a per-user HMAC proof from Relay's owner-only +bootstrap key and places the proof in the managed provider headers. It writes +the secret-bearing Codex config with an owner-only mode on Unix or a protected +owner/System DACL on Windows. The shared sidecar requires that proof before it +injects a forwarded provider credential, then removes the proof before +middleware, observability, and upstream forwarding. This prevents an unrelated +loopback caller from spending the sidecar's credentials. + +Installer-owned hook commands pin `http://127.0.0.1:47632` and their private +install-generation file explicitly. Each delivery waits for and authenticates +the MCP-managed gateway, then sends the payload once; it never starts or +recovers Relay. An ambient `NEMO_RELAY_GATEWAY_URL` cannot split hook traffic +from the required MCP-managed gateway. + +If the Relay version, user configuration, or forwarded credentials change, an +MCP client refuses to reuse the incompatible sidecar. The +`nemo-relay install codex --force` command retires an installer-owned sidecar +through its private shutdown token before refreshing the plugin. + +An existing install without MCP generation fencing cannot be retired safely, +even when Codex reports that the plugin is no longer registered, because an +already-running MCP process can outlive registration. If upgrade or uninstall +reports a missing MCP generation marker, close every Codex client and +standalone `nemo-relay mcp` process, then run: -Transparent setup injects these hooks with CLI config overrides. Plugin setup -does not install hooks from the package template directly. It writes -`features.hooks = true` in `.codex/config.toml`, configures the -`nemo-relay-openai` provider alias, and merges hook shim entries into -`.codex/hooks.json`. +```bash +codex plugin remove nemo-relay-plugin@nemo-relay-local +codex plugin marketplace remove nemo-relay-local +``` -Codex plugin mode uses hook-supervised on-demand startup only. It does not install a -user-level daemon, launchd agent, systemd user service, scheduled task, login -item, wrapper, or persistent supervisor. The sidecar starts only when a Codex -hook invokes `nemo-relay plugin-shim hook codex`. +Remove `codex-marketplace` and `codex.json` from the install directory named in +the error, then retry `nemo-relay install codex --force` to install a fenced +generation. If removal was the original goal, run `nemo-relay uninstall codex` +immediately afterward; the fenced reinstall lets Relay remove provider and hook +trust state in the same transaction. ## Transparent Setup @@ -59,10 +142,15 @@ nemo-relay run -- codex ``` The wrapper starts a per-invocation gateway on a dynamic localhost port, -enables Codex hooks with CLI config overrides, injects hook commands that use -`NEMO_RELAY_GATEWAY_URL`, and points Codex at a temporary `nemo-relay-openai` -provider alias that uses the gateway URL while preserving Codex's OpenAI auth -path. +enables Codex hooks with CLI config overrides, injects hook commands that embed +the gateway URL, and points Codex at a temporary `nemo-relay-openai` provider +alias while preserving Codex's OpenAI auth path. It trusts only the exact +generated session-hook commands and disables the known local and source Relay +plugin hook identities in Codex's process-local CLI layer. It does not replace +or rewrite the selected profile. An enabled Relay plugin MCP authenticates, +borrows, and monitors that exact dynamic gateway, while only the wrapper hooks +remain enabled for that process. Those hooks authenticate the wrapper gateway +before sending lifecycle payloads. Inspect the launch without starting Codex: @@ -73,10 +161,9 @@ nemo-relay run \ -- codex ``` -## Shared Config +## Configure Transparent Runs -Use `.nemo-relay/config.toml` for project defaults or -`~/.config/nemo-relay/config.toml` for user defaults: +Use `.nemo-relay/config.toml` for project defaults: ```toml [agents.codex] @@ -104,6 +191,32 @@ Then run: nemo-relay run --agent codex ``` +This example writes ATIF files under the project at `.nemo-relay/atif`. + +## Configure the Persistent Plugin + +Use `~/.config/nemo-relay/config.toml`, or +`$XDG_CONFIG_HOME/nemo-relay/config.toml` when `XDG_CONFIG_HOME` is set, for +persistent provider defaults. Run `nemo-relay plugins edit` without +`--project` to write user-scoped observability configuration. For example: + +```toml +version = 1 + +[[components]] +kind = "observability" +enabled = true + +[components.config.atif] +enabled = true +output_directory = "atif" +``` + +Persistent mode ignores project layers and starts the sidecar in the user Relay +configuration directory. The relative path above resolves to +`$XDG_CONFIG_HOME/nemo-relay/atif`, or `~/.config/nemo-relay/atif` when +`XDG_CONFIG_HOME` is not set. + ## Standalone Gateway Use the long-running gateway only when you do not want to launch Codex through @@ -141,13 +254,19 @@ for context. ## Verify -Run a Codex session that starts, uses one simple tool, and ends. Confirm that -ATIF was written: +Complete a Codex turn that uses one tool. For a transparent project run, +confirm that ATIF was written: ```bash ls .nemo-relay/atif ``` +For the persistent user-scoped configuration above, enter: + +```bash +ls "${XDG_CONFIG_HOME:-$HOME/.config}/nemo-relay/atif" +``` + For a direct endpoint smoke test against a manually started gateway: ```bash @@ -174,9 +293,10 @@ nemo-relay install codex `nemo-relay install codex` writes a local Codex marketplace, registers `nemo-relay-plugin`, enables Codex hooks, and configures the -`nemo-relay-openai` provider alias. Codex sidecar lifecycle remains -hook-supervised on-demand startup only; the installer does not create a wrapper or -daemon. +`nemo-relay-openai` provider alias. It writes a required MCP server entry that +invokes the resolved native `nemo-relay` binary. Installation automatically +trusts the exact plugin-owned hook definitions through `codex app-server` and +rolls back files and original trust state if activation cannot be verified. The install command requires `nemo-relay` to be available on `PATH`. It does not require launching Codex through the `nemo-relay` wrapper and does not install a @@ -191,18 +311,22 @@ codex plugin add nemo-relay-plugin@nemo-relay That path reads `.agents/plugins/marketplace.json` from the repository and installs this Codex plugin from `integrations/coding-agents/codex`. Source hooks -invoke `nemo-relay plugin-shim hook codex` directly. +use `nemo-relay hook-forward codex --forward-only` to post to the gateway +started by the required MCP entry without an installer-owned generation fence. +They cannot launch or recover Relay. -Treat the source marketplace path as discovery or manifest validation. For the -complete provider and generated-hook setup, remove the source-installed plugin -first and then run `nemo-relay install codex`. Keeping both the source plugin -and the generated install active can forward the same Codex hook twice. +Treat the source marketplace path as discovery or manifest validation. Use +`nemo-relay install codex` for the complete provider, environment-forwarding, +and verified-trust setup. Remove the source-installed plugin before using the +generated install; if both remain active and trusted, they can forward the same +lifecycle payload. Package or unpack the plugin so the plugin root contains: ```text nemo-relay-plugin/ .codex-plugin/plugin.json + .mcp.json hooks/hooks.json ``` @@ -249,11 +373,8 @@ codex plugin marketplace add "$MARKETPLACE_ROOT" codex plugin add nemo-relay-plugin@nemo-relay-local ``` -For end-to-end installation, we recommend using`nemo-relay install codex`; it performs the -marketplace registration and the persistent Codex provider/hook setup together. -If you used the manual source marketplace commands above, remove that plugin -before running the full installer so source hook templates and generated -persistent hooks do not both forward the same event. +For end-to-end installation, use `nemo-relay install codex`; it performs the +marketplace registration and persistent provider/plugin-hook setup together. The installer writes a provider alias like: @@ -266,24 +387,35 @@ base_url = "http://127.0.0.1:47632" wire_api = "responses" requires_openai_auth = true supports_websockets = false +http_headers = { "x-nemo-relay-client-token" = "" } ``` +The proof is generated by `nemo-relay install codex`; do not copy the +placeholder value from this example. + Run read-only plugin checks: ```bash nemo-relay doctor --plugin codex ``` +Doctor reports the generated MCP server, native `nemo-relay mcp` support, +plugin hook installation, environment forwarding, and live Codex trust state. +In JSON mode, inspect `checks.codex_hooks_trusted` and `codex_hook_trust` for +untrusted, modified, disabled, or missing required hook entries. + Start a normal Codex session: ```bash codex ``` -The installed hooks start the Relay sidecar lazily on -`http://127.0.0.1:47632`, and the Codex provider alias routes model traffic -through that sidecar. No launchd agent, systemd user service, scheduled task, -login item, wrapper, or persistent supervisor is installed. +Start a new CLI process after install, or restart the Codex desktop app if it +was already open, so the provider selection and hooks are reloaded. + +The required plugin MCP server starts or reuses the shared native Relay gateway +on `http://127.0.0.1:47632` before Codex begins the captured turn, and the +provider alias routes model traffic through it. To upgrade, replace the plugin directory contents with the new package for the same host, keep the same `MARKETPLACE_ROOT`, refresh the local marketplace @@ -294,17 +426,19 @@ codex plugin remove nemo-relay-plugin@nemo-relay-local codex plugin marketplace remove nemo-relay-local codex plugin marketplace add "$MARKETPLACE_ROOT" codex plugin add nemo-relay-plugin@nemo-relay-local -nemo-relay install codex +nemo-relay install codex --force ``` -To uninstall, remove NeMo Relay's Codex config and hook entries, remove the -marketplace registration, and remove the generated marketplace directory: +To uninstall, remove NeMo Relay's Codex config and exact plugin-hook trust, +remove the marketplace registration, and remove the generated marketplace +directory: ```bash nemo-relay uninstall codex ``` -Full first-request LLM capture depends on Codex firing one of the installed -hooks, especially `SessionStart` or `UserPromptSubmit`, before its first model -provider request. If a Codex version sends the provider request first, the first -request may fail or may not be captured until the next hook starts Relay. +Codex can request `/models` during provider discovery before it launches plugin +MCP servers. A cold start can produce transient connection failures that +Codex retries. Because the MCP server is required, Codex does not begin the +captured turn or send its `/responses` request until the native Relay gateway is +ready; if startup fails, the turn fails instead of silently bypassing Relay.