Skip to content

feat(hermes): add managed tool gateway broker - #3556

Closed
shannonsands wants to merge 3 commits into
NVIDIA:mainfrom
NousResearch:ns322/hermes-managed-tool-gateway-clean
Closed

feat(hermes): add managed tool gateway broker#3556
shannonsands wants to merge 3 commits into
NVIDIA:mainfrom
NousResearch:ns322/hermes-managed-tool-gateway-clean

Conversation

@shannonsands

@shannonsands shannonsands commented May 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Hermes managed Nous Tool Gateway support for OAuth/subscription onboarding while keeping raw Nous OAuth tokens out of the sandbox. NemoClaw now starts a Hermes-owned host broker for selected managed tool planes, generates broker-mode Hermes config/env, and applies broker-scoped policy presets.

Related Issue

Related to NS-322.

Changes

  • Add matrix-driven Hermes managed gateway config for web, image, audio TTS/STT, Browser Use, and optional Modal.
  • Add Hermes host broker lifecycle glue and a broker runtime under agents/hermes/host/ that refreshes Nous OAuth via x-nous-refresh-token, injects upstream auth, rotates inference agent keys, and uses a sandbox-visible opaque broker token.
  • Generate sandbox .env/config for selected managed tools and preserve the selected presets through onboarding, rebuild, connect, and status recovery paths.
  • Add nous-* policy presets that allow only host.openshell.internal:11436 service paths plus Browser Use CDP exceptions.
  • Harden the Hermes plugin shim for broker-mode dotenv hydration, URL-safety, Firecrawl paths, Browser Use state/CDP, FAL queue polling, OpenAI audio/STT, and quiet NemoClaw context.
  • Update tests/docs for managed gateway config, policies, broker auth/rotation/header normalization, and plugin shims.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Verification

  • npx prek run --all-files passes
  • npm test passes
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • make docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Additional checks run:

  • npm run build:cli
  • npx vitest run test/hermes-tool-gateway-broker.test.ts test/generate-hermes-config.test.ts test/hermes-plugin-handlers.test.ts test/policies.test.ts src/lib/onboard/initial-policy.test.ts src/lib/hermes-provider-auth.test.ts --maxWorkers=1 --testTimeout=30000
  • python3 -m py_compile agents/hermes/plugin/__init__.py
  • git diff --check --cached

Live UAT:

  • nous-web: web_search and web_extract passed through Firecrawl.
  • nous-browser: Browser Use cloud navigation/snapshot/click passed.
  • nous-audio: TTS and STT passed through OpenAI audio gateway.
  • nous-image: image generation passed through FAL queue.
  • nous-code: skipped for this UAT because Modal was not selected and terminal.backend: local was confirmed.
  • Sandbox had no raw NOUS_REFRESH_TOKEN or NOUS_ACCESS_TOKEN; gateway env/config used broker mode.

Note: I attempted the local pre-push/pre-commit hook path. The current macOS worktree does not have a complete root node_modules install, so the hook failed on local tooling resolution (biome, tsx, typescript, vitest/config) before it could provide useful PR-specific signal. The focused Hermes checks above and live UAT passed.


Signed-off-by: Shannon Sands shannon@nousresearch.com

Summary by CodeRabbit

  • New Features

    • Introduced managed tool gateways (web, audio, browser, image, code), a Hermes-managed broker service, plugin broker integrations, an in-sandbox audio transcription tool, onboarding flows to select/manage gateways, and corresponding policy presets.
  • Bug Fixes

    • OAuth refresh now sends the refresh credential via a request header instead of the form body.
  • Documentation

    • CLI/onboarding docs updated with managed gateway guidance and new environment variables.
  • Tests

    • Added unit and integration tests covering broker, presets, plugin patches, and config generation.

Review Change Stack

@copy-pr-bot

copy-pr-bot Bot commented May 15, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 6988d169-ca78-481a-a693-26a49ba1c420

📥 Commits

Reviewing files that changed from the base of the PR and between 179826c and 586f0d1.

📒 Files selected for processing (1)
  • docs/reference/commands.md
✅ Files skipped from review due to trivial changes (1)
  • docs/reference/commands.md

📝 Walkthrough

Walkthrough

Adds a Hermes managed-tool gateway broker: host-side proxy and lifecycle, onboarding selection and Dockerfile build wiring, Hermes config and plugin broker-mode patches, policy presets for managed gateways, OAuth header change, registry/session plumbing, and comprehensive tests.

Changes

Hermes Managed Tool Gateway Broker System

Layer / File(s) Summary
Gateway matrix types & loader
agents/hermes/config/managed-tool-gateway.ts
Types and loader for the managed-tool gateway matrix and config-merge helper used by Hermes config generation.
Build settings, Dockerfile args & env wiring
agents/hermes/Dockerfile, agents/hermes/config/build-env.ts, src/lib/onboard/dockerfile-patch.ts
Adds build ARG/ENV for broker/presets/token, extends build settings to include managedToolGateways, and patches staged Dockerfile with base64 presets and sanitized broker token.
Messaging env lines & config generation
agents/hermes/config/messaging-config.ts, agents/hermes/generate-config.ts
Generates messaging .env lines for broker enable/token and per-preset env entries; generate-config passes managed gateway settings through.
Hermes config integration
agents/hermes/config/hermes-config.ts
Applies selected preset config entries into generated Hermes config and wires api server toolsets and plugin enablement.
Host-side broker service
agents/hermes/host/tool-gateway-broker.ts
Adds an executable host broker that proxies tool requests, injects host OAuth/agent credentials, refreshes tokens, and normalizes proxied responses.
Broker lifecycle manager
src/lib/hermes-tool-gateway-broker.ts
Manages detached broker subprocess lifecycle, state persistence, provider registration, health checks, and ensure/restart logic.
OAuth refresh header change
src/lib/oauth-device-code.ts, src/lib/oauth-device-code.test.ts
Sends refresh token via x-nous-refresh-token header; postForm supports extraHeaders; tests updated.
Hermes provider auth integration
src/lib/hermes-provider-auth.ts, src/lib/hermes-provider-auth.test.ts
Optionally registers refresh provider with broker when presets selected, sets broker token env, and ensures broker readiness; test stubs broker.
Hermes plugin broker-mode patches
agents/hermes/plugin/__init__.py
Installs broker-mode runtime patches, pre-LLM context grounding, and registers a transcribe_audio tool routed through brokered config.
Policy presets for managed gateways
nemoclaw-blueprint/policies/presets/nous-*.yaml, agents/hermes/policy-additions.yaml
Adds nous-{web,audio,browser,image,code} presets and updates agent policy additions (managed_inference) and Nous Research pruning.
Onboarding presets & policy selection
src/lib/onboard/hermes-managed-tools.ts, src/lib/onboard/policy-selection.ts, src/lib/onboard/initial-policy.ts, src/lib/onboard/dockerfile-patch.ts, src/lib/onboard.ts
Adds preset catalog, env parsing, interactive/non-interactive selection, merges required presets into initial boot policy, wires presets through onboarding steps, and aborts/create-time registration when broker token missing.
Session & registry persistence
src/lib/state/onboard-session.ts, src/lib/state/registry.ts
Extends Session and SandboxEntry to store hermesToolGateways, sanitizes updates, initializes from overrides/JSON, and persists non-empty values to registry.
Sandbox operations wiring
src/lib/actions/sandbox/{connect,status,rebuild}.ts
Conditionally ensures broker for Hermes sandboxes in connect/status flows and preserves/passes hermesToolGateways across rebuild and resume flows.
Tests & validation
test/*, test/hermes-tool-gateway-broker.test.ts, test/hermes-plugin-handlers.test.ts, test/generate-hermes-config.test.ts, test/policies.test.ts
Adds integration and unit tests covering broker lifecycle/health, proxy semantics, token rotation, plugin patch behavior, config generation, and preset/policy validation.
Docs
docs/reference/commands.md
Documents Hermes broker on port 11436 and NEMOCLAW_HERMES_TOOL_GATEWAYS onboarding env var.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • NVIDIA/NemoClaw#3512: Modifies buildMessagingEnvLines in agents/hermes/config/messaging-config.ts—overlaps with this PR’s messaging env generation changes.
  • NVIDIA/NemoClaw#3632: Related to onboarding summary/format changes in src/lib/onboard/summary.ts.

Suggested labels

enhancement: feature, enhancement: policy

Suggested reviewers

  • ericksoa
  • cv

"🐰
I tunneled through configs, presets, and ports,
Brokered the tokens and guarded the ports,
Presets in pockets, policies tight,
Hermes now hops through a safer night,
Hooray — the sandbox routes just right!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and accurately summarizes the main feature addition: a managed tool gateway broker for Hermes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/onboard.ts (1)

10153-10178: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist cleared Hermes managed-tool selections too.

The comment above this helper says null clears and undefined leaves unchanged, but the truthy guard drops both [] and null. Switching away from Hermes or deselecting all managed tools therefore leaves the previous session value behind, and resume/drift handling can resurrect stale broker state.

Proposed fix
-  if (updates.hermesToolGateways) normalized.hermesToolGateways = updates.hermesToolGateways;
+  if (updates.hermesToolGateways !== undefined) {
+    normalized.hermesToolGateways = updates.hermesToolGateways;
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard.ts` around lines 10153 - 10178, The guard for
hermesToolGateways uses a truthy check so null and empty-array values get
dropped; change the conditional to check for undefined instead (e.g., replace
"if (updates.hermesToolGateways) normalized.hermesToolGateways =
updates.hermesToolGateways;" with "if (updates.hermesToolGateways !== undefined)
normalized.hermesToolGateways = updates.hermesToolGateways;") so that explicit
null (to clear) and [] (to deselect all) are persisted; apply the same
undefined-check pattern to any other fields using truthy guards like
messagingChannels or policyPresets if present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/onboard.ts`:
- Around line 389-423: The managed-tool onboarding helpers (e.g., the
HERMES_TOOL_GATEWAY_PRESETS constant and HERMES_TOOL_GATEWAY_PRESET_NAMES set,
plus related helper functions in the ranges around lines 1686-1935 and
9424-9687) should be extracted out of src/lib/onboard.ts into one or more
focused modules under src/lib/onboard/ (for example
src/lib/onboard/hermesGateway.ts and src/lib/onboard/managedTools.ts); move the
constants and any helper functions that build/manipulate gateway selection or
policy wiring into those new files, export the necessary symbols
(HERMES_TOOL_GATEWAY_PRESETS, HERMES_TOOL_GATEWAY_PRESET_NAMES, and their helper
functions), and update onboard.ts to import these exports so onboard.ts only
orchestrates flow and does not contain the implementation details.
- Around line 7184-7185: Call setupHermesToolGateways with the recorded/previous
gateways so the managed-tool picker is seeded from the existing sandbox
selection (e.g., change the call from setupHermesToolGateways(provider,
hermesAuthMethod) to setupHermesToolGateways(provider, hermesAuthMethod,
existing) or the local variable that holds the recorded gateways), ensuring the
existing value is passed through to avoid hermesToolGatewayDrift and unnecessary
recreates.

---

Outside diff comments:
In `@src/lib/onboard.ts`:
- Around line 10153-10178: The guard for hermesToolGateways uses a truthy check
so null and empty-array values get dropped; change the conditional to check for
undefined instead (e.g., replace "if (updates.hermesToolGateways)
normalized.hermesToolGateways = updates.hermesToolGateways;" with "if
(updates.hermesToolGateways !== undefined) normalized.hermesToolGateways =
updates.hermesToolGateways;") so that explicit null (to clear) and [] (to
deselect all) are persisted; apply the same undefined-check pattern to any other
fields using truthy guards like messagingChannels or policyPresets if present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 6483ead0-a5c2-418f-bc79-85832c4ec59f

📥 Commits

Reviewing files that changed from the base of the PR and between 76d24c7 and 174904c.

📒 Files selected for processing (35)
  • agents/hermes/Dockerfile
  • agents/hermes/config/build-env.ts
  • agents/hermes/config/hermes-config.ts
  • agents/hermes/config/managed-tool-gateway.ts
  • agents/hermes/config/messaging-config.ts
  • agents/hermes/generate-config.ts
  • agents/hermes/host/managed-tool-gateway-matrix.json
  • agents/hermes/host/tool-gateway-broker.ts
  • agents/hermes/plugin/__init__.py
  • agents/hermes/policy-additions.yaml
  • docs/reference/commands.md
  • nemoclaw-blueprint/policies/presets/nous-audio.yaml
  • nemoclaw-blueprint/policies/presets/nous-browser.yaml
  • nemoclaw-blueprint/policies/presets/nous-code.yaml
  • nemoclaw-blueprint/policies/presets/nous-image.yaml
  • nemoclaw-blueprint/policies/presets/nous-web.yaml
  • src/lib/actions/inference-set.test.ts
  • src/lib/actions/sandbox/connect.ts
  • src/lib/actions/sandbox/rebuild.ts
  • src/lib/actions/sandbox/status.ts
  • src/lib/hermes-provider-auth.test.ts
  • src/lib/hermes-provider-auth.ts
  • src/lib/hermes-tool-gateway-broker.ts
  • src/lib/oauth-device-code.test.ts
  • src/lib/oauth-device-code.ts
  • src/lib/onboard.ts
  • src/lib/onboard/dockerfile-patch.ts
  • src/lib/onboard/initial-policy.test.ts
  • src/lib/onboard/initial-policy.ts
  • src/lib/state/onboard-session.ts
  • src/lib/state/registry.ts
  • test/generate-hermes-config.test.ts
  • test/hermes-plugin-handlers.test.ts
  • test/hermes-tool-gateway-broker.test.ts
  • test/policies.test.ts

Comment thread src/lib/onboard.ts Outdated
Comment thread src/lib/onboard.ts Outdated
@wscurran wscurran added the integration: hermes Hermes integration behavior label May 15, 2026
@wscurran

Copy link
Copy Markdown
Contributor

✨ Thanks for submitting this detailed PR to add Hermes managed Nous Tool Gateway support. This change aims to improve the onboarding process by keeping raw Nous OAuth tokens out of the sandbox and generating broker-mode Hermes config/env.

Signed-off-by: Test User <test@example.com>
@shannonsands
shannonsands force-pushed the ns322/hermes-managed-tool-gateway-clean branch from 174904c to 179826c Compare May 18, 2026 03:48

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/onboard.ts (1)

9746-9756: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve the sandbox name before re-running Hermes inference on resume.

This branch can execute on a resumed session that finished provider/inference setup but never reached sandbox creation, so sandboxName is still null by design. setupInference() now hard-fails on Line 7450 for Hermes, which turns a valid resume into an immediate abort.

Suggested fix
       if (provider === hermesProviderAuth.HERMES_PROVIDER_NAME) {
+        if (!sandboxName) {
+          sandboxName = await promptValidatedSandboxName(agent);
+        }
         startRecordedStep("inference", { provider, model });
         const inferenceResult = await setupInference(
           sandboxName,
           model,
           provider,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard.ts` around lines 9746 - 9756, When provider ===
hermesProviderAuth.HERMES_PROVIDER_NAME ensure sandboxName is resolved before
calling setupInference: check if sandboxName is null/undefined and, if so, await
the existing sandbox resolution/creation routine used elsewhere in this module
to populate sandboxName, then proceed to startRecordedStep("inference", ...) and
call setupInference(sandboxName, ...); reference the symbols provider,
hermesProviderAuth.HERMES_PROVIDER_NAME, sandboxName, and setupInference to
locate where to insert the resolution call.
🧹 Nitpick comments (1)
agents/hermes/Dockerfile (1)

92-110: 💤 Low value

Acknowledge: Broker token baked into image layer by design.

Trivy flags NEMOCLAW_HERMES_TOOL_BROKER_TOKEN as potential secret exposure (DS-0031). Per the PR objectives, this is intentional—the broker token is an opaque, sandbox-visible credential that replaces raw Nous OAuth tokens. The same pattern already exists for NEMOCLAW_PROVIDER_KEY.

For future consideration: if these tokens become more sensitive, consider injecting them at runtime via mounted secrets rather than build args, but the current approach aligns with the broker architecture.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agents/hermes/Dockerfile` around lines 92 - 110, Add a short inline comment
above the ARG/ENV block stating that baking NEMOCLAW_HERMES_TOOL_BROKER_TOKEN
into the image is intentional (matches existing NEMOCLAW_PROVIDER_KEY pattern)
and that this is an opaque, sandbox-visible broker credential; reference the ARG
NEMOCLAW_HERMES_TOOL_BROKER_TOKEN and ENV NEMOCLAW_HERMES_TOOL_BROKER_TOKEN by
name and mention the Trivy DS-0031 false-positive, and optionally note the
future alternative of runtime-mounted secrets if sensitivity increases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/lib/onboard.ts`:
- Around line 9746-9756: When provider ===
hermesProviderAuth.HERMES_PROVIDER_NAME ensure sandboxName is resolved before
calling setupInference: check if sandboxName is null/undefined and, if so, await
the existing sandbox resolution/creation routine used elsewhere in this module
to populate sandboxName, then proceed to startRecordedStep("inference", ...) and
call setupInference(sandboxName, ...); reference the symbols provider,
hermesProviderAuth.HERMES_PROVIDER_NAME, sandboxName, and setupInference to
locate where to insert the resolution call.

---

Nitpick comments:
In `@agents/hermes/Dockerfile`:
- Around line 92-110: Add a short inline comment above the ARG/ENV block stating
that baking NEMOCLAW_HERMES_TOOL_BROKER_TOKEN into the image is intentional
(matches existing NEMOCLAW_PROVIDER_KEY pattern) and that this is an opaque,
sandbox-visible broker credential; reference the ARG
NEMOCLAW_HERMES_TOOL_BROKER_TOKEN and ENV NEMOCLAW_HERMES_TOOL_BROKER_TOKEN by
name and mention the Trivy DS-0031 false-positive, and optionally note the
future alternative of runtime-mounted secrets if sensitivity increases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 16f12a28-8e51-4a31-9ec6-1aa4c7c4e403

📥 Commits

Reviewing files that changed from the base of the PR and between 174904c and 179826c.

📒 Files selected for processing (32)
  • agents/hermes/Dockerfile
  • agents/hermes/config/build-env.ts
  • agents/hermes/config/hermes-config.ts
  • agents/hermes/config/managed-tool-gateway.ts
  • agents/hermes/config/messaging-config.ts
  • agents/hermes/generate-config.ts
  • agents/hermes/host/managed-tool-gateway-matrix.json
  • agents/hermes/host/tool-gateway-broker.ts
  • agents/hermes/plugin/__init__.py
  • agents/hermes/policy-additions.yaml
  • docs/reference/commands.md
  • nemoclaw-blueprint/policies/presets/nous-audio.yaml
  • nemoclaw-blueprint/policies/presets/nous-browser.yaml
  • nemoclaw-blueprint/policies/presets/nous-code.yaml
  • nemoclaw-blueprint/policies/presets/nous-image.yaml
  • nemoclaw-blueprint/policies/presets/nous-web.yaml
  • src/lib/actions/inference-set.test.ts
  • src/lib/actions/sandbox/connect.ts
  • src/lib/actions/sandbox/rebuild.ts
  • src/lib/actions/sandbox/status.ts
  • src/lib/hermes-provider-auth.test.ts
  • src/lib/hermes-provider-auth.ts
  • src/lib/hermes-tool-gateway-broker.ts
  • src/lib/oauth-device-code.test.ts
  • src/lib/oauth-device-code.ts
  • src/lib/onboard.ts
  • src/lib/onboard/dockerfile-patch.ts
  • src/lib/onboard/hermes-managed-tools.ts
  • src/lib/onboard/initial-policy.test.ts
  • src/lib/onboard/initial-policy.ts
  • src/lib/onboard/policy-selection.ts
  • src/lib/onboard/summary.ts
✅ Files skipped from review due to trivial changes (2)
  • src/lib/actions/inference-set.test.ts
  • src/lib/oauth-device-code.test.ts
🚧 Files skipped from review as they are similar to previous changes (21)
  • src/lib/onboard/initial-policy.ts
  • src/lib/onboard/dockerfile-patch.ts
  • src/lib/hermes-provider-auth.ts
  • nemoclaw-blueprint/policies/presets/nous-code.yaml
  • docs/reference/commands.md
  • src/lib/actions/sandbox/status.ts
  • src/lib/actions/sandbox/connect.ts
  • nemoclaw-blueprint/policies/presets/nous-web.yaml
  • nemoclaw-blueprint/policies/presets/nous-image.yaml
  • nemoclaw-blueprint/policies/presets/nous-audio.yaml
  • agents/hermes/config/build-env.ts
  • agents/hermes/generate-config.ts
  • agents/hermes/host/managed-tool-gateway-matrix.json
  • nemoclaw-blueprint/policies/presets/nous-browser.yaml
  • src/lib/hermes-provider-auth.test.ts
  • agents/hermes/config/managed-tool-gateway.ts
  • src/lib/hermes-tool-gateway-broker.ts
  • src/lib/oauth-device-code.ts
  • agents/hermes/policy-additions.yaml
  • src/lib/actions/sandbox/rebuild.ts
  • agents/hermes/host/tool-gateway-broker.ts

cv pushed a commit that referenced this pull request May 19, 2026
## Summary
Maintainer replay of #3556 from @shannonsands / NousResearch, with one
maintainer follow-up commit on top to make the branch shippable.

This keeps the original Hermes managed Nous Tool Gateway support:
OAuth/subscription onboarding without raw Nous OAuth tokens in the
sandbox, a Hermes-owned host broker for selected managed tool planes,
broker-mode Hermes config/env generation, and broker-scoped policy
presets.

## Maintainer Follow-Up
- Resolves the Hermes resume correctness blocker where
`setupInference()` could be called with `sandboxName === null` when the
provider/model route was already selected and ready.
- Preserves the #2753 behavior: the resumed sandbox name is resolved
before Hermes inference reconciliation, but is not persisted to the
onboard session before sandbox creation.
- Updates the Hermes provider reuse test for the intentional
provider-store preflight and adds a regression for resume with no
recorded sandbox name.

## Original PR
Supersedes/continues #3556.

Original author: @shannonsands
Original PR head before this maintainer patch:
`4db9a302c3e385978a12eccc3d9f748c4a91939e`
Maintainer patch commit: `3f767eeff503a1d36827022033ad3aa075b0b7e3`

## Verification
- [x] `npm run build:cli`
- [x] `npx vitest run test/onboard.test.ts -t "Hermes Provider|sandbox
name before"`
- [x] `npx vitest run test/onboard.test.ts
test/onboard-resume-provider-recovery.test.ts
test/onboard-policy-suggestions.test.ts
src/lib/state/onboard-session.test.ts
src/lib/onboard/dockerfile-patch.test.ts
src/lib/hermes-provider-auth.test.ts src/lib/oauth-device-code.test.ts
test/generate-hermes-config.test.ts test/hermes-plugin-handlers.test.ts
test/hermes-tool-gateway-broker.test.ts test/policies.test.ts`
- [x] `npm run checks`
- [x] `git diff --check`

## Merge Gate
Before approval/merge, still require one live Hermes OAuth onboarding
smoke with at least `nous-web`, broker health on
`127.0.0.1:11436/health`, and one managed-tool call through the sandbox.
If local Nous OAuth/subscription is unavailable, get that exact smoke
result from Nous before merging.

Signed-off-by: Shannon Sands <shannon@nousresearch.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Interactive onboarding and selection for five managed Nous tools
(audio, browser, code, image, web).
* Local host-side managed-tool gateway broker and runtime “broker-mode”
plugin; new audio transcription handler.

* **Improvements**
* Broker-aware onboarding, OAuth/device flows, token
registration/persistence, and sandbox lifecycle preservation of
managed-tool selections.
* Generated runtime config/env support for enabling broker and presets;
new policy presets enforcing network and binary restrictions.

* **Documentation**
  * Docs updated with broker behavior and environment options.

* **Tests**
* Added unit and integration tests for broker, plugin, and config
generation.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/NVIDIA/NemoClaw/pull/3742?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
@wscurran wscurran added area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery and removed Sandbox labels Jun 3, 2026
@cv cv self-assigned this Jun 3, 2026
@wscurran wscurran added the feature PR adds or expands user-visible functionality label Jun 8, 2026
@cv

cv commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Manual PR Review Advisor result

This PR Review Advisor analysis was run manually via workflow_dispatch, so the workflow did not post its usual sticky comment. Posting the advisor summary here because it produced actionable findings.

Run: https://github.com/NVIDIA/NemoClaw/actions/runs/28205896026


PR Review Advisor

The managed-tool feature is well covered at unit level, but the new host credential broker currently exposes a sandbox-visible bearer token through an all-interface listener and image/env surfaces.

Required before merge

  • Constrain the host broker before exposing sandbox bearer tokens (agents/hermes/host/tool-gateway-broker.ts:590): The new broker listens on 0.0.0.0:11436 while the sandbox receives a reusable broker token in its generated environment. Proxy routes authenticate the token, but they do not restrict the remote address to loopback or the expected sandbox/bridge networks, and /health is unauthenticated. Because this process injects Nous access tokens upstream and rotates OpenShell inference credentials, it should fail closed at the network boundary instead of being reachable on every host interface.
    • Impact: Any host- or LAN-reachable client that obtains the broker token can spend the user's managed-tool subscription through the host broker. Even without the token, unauthenticated health/service enumeration leaks that the credential broker is running.
    • Recommendation: Bind the broker only to the intended local/sandbox-facing interface or enforce a strict remote-address allowlist for proxy routes, and keep unauthenticated endpoints loopback-only or non-enumerating. Document the intended source boundary and reject requests outside it before token refresh or upstream proxying.
    • Verification hint: Read server.listen(PORT, "0.0.0.0", ...) and handleProxy() in agents/hermes/host/tool-gateway-broker.ts; confirm whether any proxy route checks req.socket.remoteAddress before accepting Authorization or x-api-key.
    • Missing regression test: Add a broker test that sends a valid broker token from a simulated non-loopback/non-sandbox remote address and expects 403/404 without calling the portal or upstream; add a positive case for the exact allowed sandbox/loopback source.
    • Evidence: server.listen(PORT, "0.0.0.0", ...) exposes the HTTP server on all interfaces; only /internal/refresh-inference checks remoteAddress, while proxy routes rely solely on the bearer token.
  • Do not bake the broker token into the Docker build/image contract (src/lib/onboard/dockerfile-patch.ts:229): When managed tools are selected, patchStagedDockerfile() writes ARG NEMOCLAW_HERMES_TOOL_BROKER_TOKEN=<token>, and the Hermes Dockerfile promotes that ARG into an ENV used to generate .hermes/.env with TOOL_GATEWAY_USER_TOKEN=<token>. This token is the bearer credential for the host broker, so build args, staged Dockerfiles, image config/history, and generated env files become credential-bearing artifacts.
    • Impact: A local user, build cache, image inspector, or artifact collector that can read the staged Dockerfile/image metadata may recover the broker token and call the host broker while it is reachable. This weakens the PR's stated boundary that raw OAuth stays host-managed by replacing it with a long-lived sandbox-visible bearer secret baked into the image.
    • Recommendation: Inject the broker token at sandbox runtime through the existing OpenShell provider/credential placeholder path or another runtime-only secret mechanism. Avoid putting the token in Dockerfile ARG/ENV or any layer/config metadata; if .env must contain a value, make it a placeholder resolved by the broker boundary rather than the raw token.
    • Verification hint: Read patchStagedDockerfile() around the NEMOCLAW_HERMES_TOOL_BROKER_TOKEN replacement and agents/hermes/Dockerfile where the ARG is promoted into ENV, then inspect buildMessagingEnvLines() which emits TOOL_GATEWAY_USER_TOKEN=${managedToolBrokerToken}.
    • Missing regression test: Add a config/build test that stages a Hermes managed-tool Dockerfile with a sentinel broker token and asserts the staged Dockerfile and generated image config inputs do not contain the sentinel while the sandbox runtime still receives a resolvable broker credential.
    • Evidence: The diff adds ARG NEMOCLAW_HERMES_TOOL_BROKER_TOKEN, ENV NEMOCLAW_HERMES_TOOL_BROKER_TOKEN=..., and envLines.push(TOOL_GATEWAY_USER_TOKEN=${managedToolBrokerToken}).
  • Extract the managed-tool orchestration instead of growing onboard.ts further (src/lib/onboard.ts:4747): This PR adds managed-tool selection, credential preparation, sandbox creation args, Dockerfile patch inputs, resume drift detection, registry/session persistence, and policy merging across src/lib/onboard.ts. The synthetic drift signal shows this already-large onboarding file grew by thousands of lines in this PR despite the new hermes-managed-tools.ts helper.
    • Impact: Keeping this feature spread through the onboarding monolith makes credential and policy invariants harder to audit, especially around OAuth, broker token preparation, sandbox rebuild/reuse, and required policy presets.
    • Recommendation: Move the Hermes managed-tool orchestration into a cohesive module that owns selection, drift detection, broker-token readiness, Dockerfile inputs, and required policy presets. Leave onboard.ts as narrow call sites that pass the resulting plan into existing sandbox creation flows.
    • Verification hint: Search src/lib/onboard.ts for hermesToolGateways and review the call sites from provider selection through sandbox creation and policy application.
    • Missing regression test: Keep or add behavior tests around the extracted orchestration: OAuth mode selects presets, API-key mode rejects/ignores requested presets, drift detection triggers recreate, and required presets are carried into create-time and final policy application.
    • Evidence: The changed file contains numerous hermesToolGateways call sites across provider selection, inference setup, sandbox creation, resume/reuse, registry update, and policy support.

Resolve or justify before merge

  • Source-of-truth review needed: Hermes broker URL-safety monkeypatch: The advisor marked localized patch analysis as needs_followup.
    • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Verification hint: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
    • Missing regression test: Existing tests do not cover _broker_safe_url() negative SSRF inputs.
    • Evidence: Covered by the finding Add negative coverage before replacing Hermes' DNS-based SSRF guard.
  • Source-of-truth review needed: Browser Use CDP tunnel and session-state patches: The advisor marked localized patch analysis as needs_followup.
    • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Verification hint: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
    • Missing regression test: The plugin test verifies cache reset and local-session eviction stubs, but not tunnel source validation, proxy CONNECT behavior, or subprocess cleanup.
    • Evidence: Covered by the architecture finding on documenting/removing the Hermes monkeypatch layer.
  • Source-of-truth review needed: Connect-time broker recovery: The advisor marked localized patch analysis as missing.
    • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Verification hint: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
    • Missing regression test: No focused connect recovery test was found in the changed test inventory.
    • Evidence: Covered by the finding Make managed-tool broker recovery observable instead of silently best-effort.
  • Add negative coverage before replacing Hermes' DNS-based SSRF guard (agents/hermes/plugin/init.py:208): Broker mode monkeypatches Hermes URL safety with _broker_safe_url(), which intentionally stops doing sandbox-local DNS resolution and allows any multi-label non-literal hostname that is not on a small local suffix/blocklist. That may be necessary for brokered tools, but it changes a security boundary and currently has no focused tests for URL-obfuscation or private-resolution edge cases.
    • Impact: A missed hostname/IP representation can turn a brokered web/browser request into an SSRF primitive against metadata, localhost, or private infrastructure if an upstream gateway follows the supplied URL. The policy and broker routing are strong only if the preflight continues to reject all local/private target forms.
    • Recommendation: Keep the DNS-independent broker-mode guard only if it has explicit negative tests and documented ownership. Cover private IPv4/IPv6 literals, metadata IPs, localhost aliases, CGNAT, numeric/octal/hex encodings, trailing dots, single-label names, .local/.internal suffixes, and allowed public-host examples.
    • Verification hint: Read _broker_safe_url() and _install_broker_url_safety_patch() in agents/hermes/plugin/__init__.py; compare with test/hermes-plugin-handlers.test.ts, which verifies patch installation but not SSRF-negative inputs.
    • Missing regression test: Add a Python plugin test named like broker_safe_url rejects metadata, private, localhost, numeric, and local-suffix targets while allowing normal public HTTPS hostnames.
    • Evidence: _install_broker_url_safety_patch() assigns module.is_safe_url = _broker_safe_url; existing plugin tests exercise managed-tool patching, FAL rewriting, and Firecrawl path preservation but not URL-safety denial cases.
  • Cap broker request bodies before buffering them in memory (agents/hermes/host/tool-gateway-broker.ts:382): readRequestBody() collects all request chunks into an array and concatenates them without a maximum size. Because the broker is a new host-side HTTP listener and currently binds all interfaces, a client can send arbitrarily large bodies to authenticated or unauthenticated routes that reach handleProxy().
    • Impact: An oversized request can consume host memory or degrade the local NemoClaw session before the broker ever reaches the managed upstream. This is especially risky for a long-running detached credential broker.
    • Recommendation: Enforce a conservative maximum body size per service or globally, destroy/drain the request once exceeded, and return 413 without refreshing OAuth or contacting the upstream.
    • Verification hint: Read readRequestBody(req) and handleProxy() in agents/hermes/host/tool-gateway-broker.ts; there is no byte counter or size guard before Buffer.concat(chunks).
    • Missing regression test: Add a broker test that posts a body larger than the configured limit with a valid broker token and asserts 413 plus zero portal/upstream calls.
    • Evidence: req.on("data", (chunk) => chunks.push(chunk)); req.on("end", () => resolve(Buffer.concat(chunks))); has no limit.
  • Remove @ts-nocheck from the new credential broker surfaces (agents/hermes/host/tool-gateway-broker.ts:2): Both the host broker runtime and its lifecycle wrapper disable TypeScript checking even though they implement token hashing, auth header rewriting, provider updates, state-file parsing, and process lifecycle recovery. These are trust-boundary files where type drift can become a security or availability bug.
    • Impact: Mistyped state fields, missing env values, or unexpected request/header shapes can bypass checks or break refresh/recovery without compiler feedback. The tests cover happy paths and a few negatives, but they do not replace type checking for this much new security-sensitive code.
    • Recommendation: Type the broker runtime and wrapper, or keep only a minimal CommonJS shim unchecked while moving the auth/proxy/state logic into checked TypeScript modules.
    • Verification hint: Read the first lines of agents/hermes/host/tool-gateway-broker.ts and src/lib/hermes-tool-gateway-broker.ts; both start with // @ts-nocheck.
    • Missing regression test: Existing broker tests exercise refresh, rotation, and header rewriting; after removing @ts-nocheck, add type-level coverage by compiling these files as part of the normal CLI build.
    • Evidence: The diff introduces // @ts-nocheck on new files that handle OAuth refresh credentials and host broker lifecycle.
  • Make managed-tool broker recovery observable instead of silently best-effort (src/lib/actions/sandbox/connect.ts:555): connectSandbox() tries to start the Hermes tool broker for matching registry entries, but maybeEnsureHermesToolGatewayBroker() catches all failures and emits no warning. If the detached broker is gone after a host reboot and no refresh token is available in the current process, connect succeeds and the user discovers the failure only when managed tools return broker guidance later.
    • Impact: The PR's recovery/preservation path can look healthy while selected managed tools are unavailable. This creates confusing runtime failures and makes it hard to distinguish a sandbox problem from a missing host credential broker.
    • Recommendation: Return and surface the broker recovery result for Hermes sandboxes with selected managed tools. Warn or fail with the re-auth/resume instruction before entering the sandbox when the broker cannot be made healthy.
    • Verification hint: Read maybeEnsureHermesToolGatewayBroker() in src/lib/actions/sandbox/connect.ts and ensureHermesToolGatewayBroker() in src/lib/hermes-tool-gateway-broker.ts; false or thrown results are swallowed.
    • Missing regression test: Add a connect-action test where a Hermes registry entry has hermesToolGateways: ["nous-web"] and broker recovery returns false, asserting the CLI emits a managed-tool broker recovery warning/instruction instead of silently continuing.
    • Evidence: The catch block in maybeEnsureHermesToolGatewayBroker() contains only /* non-fatal — managed-tool calls will surface broker guidance if needed */.
  • Cover rebuild preservation for hermesToolGateways (src/lib/actions/sandbox/rebuild.ts:548): The rebuild path now snapshots sb.hermesToolGateways before deleting the sandbox registry entry and writes the selected presets back into the onboard session. This is the right shape, but the changed tests do not appear to exercise the destroy/recreate window for Hermes managed-tool selections.
    • Impact: A regression here would rebuild a Hermes sandbox without the selected managed-tool policy/config, or incorrectly revive/clear presets from stale session state.
    • Recommendation: Add a focused rebuild unit test that proves registry values win over stale session values and survive the registry removal before onboard --resume.
    • Verification hint: Read the rebuildHermesToolGateways logic in src/lib/actions/sandbox/rebuild.ts; compare the changed test inventory, which has no named rebuild test for Hermes managed-tool preservation.
    • Missing regression test: Add rebuild preserves Hermes managed-tool gateway presets from the registry after deleting the old sandbox entry and does not fall back to stale session presets.
    • Evidence: The diff adds registryHermesToolGateways, sessionHermesToolGateways, and s.hermesToolGateways = ... in rebuild, but the static test inventory lists no corresponding rebuild test.
  • Document removal conditions and source ownership for the Hermes monkeypatch layer (agents/hermes/plugin/init.py:252): The plugin now installs a broad set of monkeypatches for managed gateway auth, SSRF URL safety, OpenAI audio selection, transcription, FAL queue polling URLs, Firecrawl URL joining, Browser Use CDP tunneling, and browser session cache eviction. Several comments describe the invalid upstream behavior, but most do not identify an upstream issue/version, why the source cannot be fixed in this PR, or when the workaround can be removed.
    • Impact: Without source-of-truth boundaries, future Hermes upgrades can silently invalidate these patches or leave NemoClaw carrying custom security-sensitive behavior after upstream support exists. This is especially risky where the workaround changes SSRF validation and opens a local CDP tunnel.
    • Recommendation: For each monkeypatch, add a compact source-of-truth note or table naming the invalid upstream state, source module/version, regression test, and removal condition. Where possible, shrink the plugin to a single broker integration seam and avoid patching unrelated internals.
    • Verification hint: Scan agents/hermes/plugin/__init__.py for _install_*_patch functions and compare with test/hermes-plugin-handlers.test.ts; only some workaround behaviors have direct assertions and none encode removal conditions.
    • Missing regression test: Add or identify tests for each retained patch, especially broker URL safety rejects private targets and Browser Use CDP tunnel only accepts wss://*.browser-use.com URLs and cleans up its subprocess.
    • Evidence: The plugin grows to over a thousand lines and defines many _install_*_patch functions; the existing plugin test covers patch effects for selected stubs but not source/removal contracts.

In-scope improvements

  • None.

Test follow-ups to resolve or justify

  • Runtime validation — Hermes sandbox with nous-web can reach host.openshell.internal:11436/firecrawl but cannot reach firecrawl-gateway.nousresearch.com directly.. The PR changes Dockerfile generation, sandbox policy, host credential broker lifecycle, OpenShell provider updates, and Hermes runtime monkeypatches. Unit tests cover important pieces, but sandbox/network behavior and credential exposure require runtime or integration validation.
  • Runtime validation — Hermes host broker rejects a valid broker token from a non-loopback/non-sandbox source address without refreshing OAuth or contacting upstream.. The PR changes Dockerfile generation, sandbox policy, host credential broker lifecycle, OpenShell provider updates, and Hermes runtime monkeypatches. Unit tests cover important pieces, but sandbox/network behavior and credential exposure require runtime or integration validation.
  • Runtime validation — Staged Hermes managed-tool Dockerfile and generated image config inputs do not contain the raw nc_broker_... token.. The PR changes Dockerfile generation, sandbox policy, host credential broker lifecycle, OpenShell provider updates, and Hermes runtime monkeypatches. Unit tests cover important pieces, but sandbox/network behavior and credential exposure require runtime or integration validation.
  • Runtime validation — Broker rejects oversized managed-tool request bodies with 413 and makes zero portal/upstream calls.. The PR changes Dockerfile generation, sandbox policy, host credential broker lifecycle, OpenShell provider updates, and Hermes runtime monkeypatches. Unit tests cover important pieces, but sandbox/network behavior and credential exposure require runtime or integration validation.
  • Runtime validation_broker_safe_url rejects localhost, metadata IPs, RFC1918/CGNAT, IPv6 local, numeric/obfuscated IP hostnames, single-label hosts, and local suffixes while allowing normal public HTTPS hostnames.. The PR changes Dockerfile generation, sandbox policy, host credential broker lifecycle, OpenShell provider updates, and Hermes runtime monkeypatches. Unit tests cover important pieces, but sandbox/network behavior and credential exposure require runtime or integration validation.
  • Cover rebuild preservation for hermesToolGateways — Add a focused rebuild unit test that proves registry values win over stale session values and survive the registry removal before onboard --resume.
  • Acceptance clause: Related to NS-322. — add test evidence or identify existing coverage. No deterministic linked issue or NS-322 clauses/comments were available in the review context, so there is no external acceptance contract to map beyond the PR body.
  • Acceptance clause: Add Hermes host broker lifecycle glue and a broker runtime under agents/hermes/host/ that refreshes Nous OAuth via x-nous-refresh-token, injects upstream auth, rotates inference agent keys, and uses a sandbox-visible opaque broker token. — add test evidence or identify existing coverage. agents/hermes/host/tool-gateway-broker.ts, src/lib/hermes-tool-gateway-broker.ts, and src/lib/hermes-provider-auth.ts implement the broker and OAuth handoff; test/hermes-tool-gateway-broker.test.ts covers refresh header, auth replacement, response normalization, and rotation. Security findings remain for all-interface exposure and token delivery.

What looks good

  • The broker test covers refresh-token transport via x-nous-refresh-token, upstream auth replacement, response header normalization, refresh rotation, and secret redaction from broker output.
  • New policy preset tests verify the nous-* presets are listed, avoid direct vendor gateway hosts, and keep Browser Use CDP exceptions scoped to the browser preset.
  • OAuth/provider tests show raw OAuth is used only to mint/register OpenShell credentials and no legacy raw OAuth state directory is created.
  • The matrix-driven config generation is covered by tests that assert selected managed-tool presets produce the expected Hermes config and .env entries.

@cv

cv commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Thanks for this contribution, @shannonsands. The managed tool gateway work from this branch was replayed with attribution, completed with the missing Hermes resume fix and regression coverage, and merged in #3742.

Current main has since received additional secret-boundary hardening in #4771 and substantial onboarding, policy, messaging, and lifecycle refactors. Comparing this branch with current main found no desired behavior unique to #3556; bringing it forward would instead produce 31 conflicts and reintroduce older token-delivery and broker behavior that later changes intentionally replaced.

I have removed the stale v0.0.81 target and am closing this PR as superseded by #3742. Any newly discovered gap in the current implementation should be handled as a focused change from current main. Thank you again for the original implementation and UAT work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery feature PR adds or expands user-visible functionality integration: hermes Hermes integration behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants