feat(cli): let a launched pi session name its model upstream - #2
Closed
yczhang-nv wants to merge 1 commit into
Closed
feat(cli): let a launched pi session name its model upstream#2yczhang-nv wants to merge 1 commit into
yczhang-nv wants to merge 1 commit into
Conversation
The gateway forwards each API family to one statically configured upstream, so pi could only redirect a model whose endpoint was already that upstream. Everything else refused -- `upstream-mismatch` or `unknown-upstream` -- because redirecting past a mismatch does not cost spans, it sends the request to the wrong provider and breaks the session. That refusal is the common case for pi: 32 of its 39 providers speak an API the gateway serves, but only the two it is configured for are an endpoint some model already calls. A launched session can now name the endpoint, in `x-nemo-relay-upstream-base-url` on each request, so one gateway fronts a provider it was never configured for and those models get LLM spans and model-call enforcement. **What makes the header safe to honor.** It is a client telling a gateway where to send credentialed traffic, so the question is whether the client is the agent this invocation launched, and the transparent proxy credential is what answers it: minted per run, given only to the process the launcher starts, and already sent on redirected providers since `a16c9659`. Without it the header is treated as absent and configured routing applies unchanged, so a standalone `nemo-relay --bind` daemon -- which mints no credential -- is unaffected. Deliberately *not* a widening of `x-nemo-relay-internal-dispatch-url`. That one redirects a single request and is stripped from every inbound request precisely so a client cannot steer the gateway; it belongs to request intercepts, which are trusted plugin code. A separate header keeps both statements true and checkable. The new one is also stripped before forwarding: it addresses this gateway, and means nothing to a provider. **The provider-wide constraint survives, with a different question.** `registerProvider` rewrites every model of a provider, so naming one endpoint commits its siblings to it. The sibling scan therefore still runs; on the static path it asks whether every model already targets the gateway's upstream, and on the named path whether every model shares the endpoint about to be named. A provider that mixes endpoints is refused either way, which is the same broken session the static path exists to prevent. One existing test changed meaning rather than breaking: a mismatched upstream *with* a credential now registers, which is the feature. Its invariant -- that a provider Relay did not redirect never sees the credential -- is unchanged and still asserted, now via the case that still refuses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Yuchen Zhang <yuchenz@nvidia.com>
Owner
Author
|
Closing: this should target the upstream repository, not the fork. The branch stays for review; the formal PR will be opened against NVIDIA/NeMo-Relay. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Stacked on NVIDIA/NeMo-Relay#804. Base is
feat/pi-integration, so the diff here is one commit. Opened on the fork because the upstream repository has nofeat/pi-integrationbranch to target; retarget tomainonce NVIDIA#804 merges.Lets a launched pi session tell the gateway where to forward its model traffic, so a provider the gateway was never configured for still produces LLM spans and is still subject to model-call policy.
The gateway forwards each API family to one statically configured upstream, so pi could only redirect a model whose endpoint was already that upstream. Everything else refused —
upstream-mismatchorunknown-upstream— because redirecting past a mismatch does not cost spans, it reaches the wrong provider and breaks the session. That refusal is the common case for pi: 32 of its 39 providers speak an API the gateway serves, but only the two it is configured for are an endpoint some model already calls.Details
The extension sends
x-nemo-relay-upstream-base-urlon each request for a redirected provider, and the gateway forwards there instead of to its configured upstream.What makes the header safe to honor. It is a client telling a gateway where to send credentialed traffic, so the question is whether that client is the agent this invocation launched — and the transparent proxy credential answers it. The launcher mints it per run and gives it only to the process it starts, and the extension already sends it on redirected providers. Without it the header is treated as absent and configured routing applies unchanged, so a standalone
nemo-relay --binddaemon, which mints no credential, is unaffected.Deliberately not a widening of
x-nemo-relay-internal-dispatch-url. That header already redirects a single request, and is stripped from every inbound request precisely so a client cannot steer the gateway — it belongs to request intercepts, which are trusted plugin code running inside it. Widening it would hand that authority to anything that can reach the port. A separate header keeps both statements true and checkable. The new one is also stripped before forwarding upstream: it addresses this gateway and means nothing to a provider.The provider-wide constraint survives, with a different question.
registerProviderrewrites every model of a provider, so naming one endpoint commits its siblings to it. The sibling scan still runs: on the static path it asks whether every model already targets the gateway's upstream, and on the named path whether every model shares the endpoint about to be named. A provider that mixes endpoints — Fireworks — is refused either way.One existing test changed meaning rather than breaking. A mismatched upstream with a credential now registers, which is the feature. Its invariant — that a provider Relay did not redirect never sees the credential — is unchanged and still asserted, through the case that still refuses.
Where should the reviewer start?
crates/cli/src/agents/pi/alignment.rs. It is the whole trust decision in one function, and its module comment states the threat model.Then
crates/cli/tests/coverage/agents/pi_alignment_tests.rs, wherean_unauthenticated_request_cannot_name_an_upstreamis the security boundary written as a test, andonly_absolute_http_urls_with_a_bare_host_are_acceptedcovers the URL shapes that would otherwise reach somewhere they should not.Known limitations
nemo-relay --binddaemon still cannot reach an arbitrary provider, because it issues no credential to check against. Static configuration remains the answer there.NEMO_RELAY_PI_REDIRECT=forceis unchanged and still redirects without naming an endpoint.Validation
cargo nextest run -p nemo-relay-cli— 1411 passed (1402 before; 9 added).just test-pi— 108 passed (99 before; 9 added),tscclean.just docs-linkcheck— 0 errors.cargo clippy --workspace --all-targets -- -D warningsclean.uv run pre-commitclean on the changed files.Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)
🤖 Generated with Claude Code