Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .agents/skills/add-middleware/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ middleware behavior to a new pipeline stage.

Decide these before editing code:

- Is this for tools, LLMs, or both?
- Is this for tools, LLMs, marks, scope events, or a combination?
- Is it a conditional guardrail, sanitize guardrail, request intercept, or
execution intercept?
- Does it run on request input, inner callable execution, stream chunks, or
Expand All @@ -33,6 +33,8 @@ Decide these before editing code:
- Does it need both global and scope-local registration?
- What should subscribers observe in `event.input` and `event.output` after this
middleware runs?
- If this is an event sanitizer, which of `data`, `category_profile`, and
`metadata` may change, and is the event used only as immutable context?

## Pipeline Order

Expand All @@ -44,6 +46,9 @@ See `docs/about/concepts/middleware.md` for the full diagrams.
- **LLM execute**:
conditional guardrails -> request intercepts -> sanitize request (for events)
| execution intercept chain(callable) -> sanitize response
- **Mark and scope events**:
specialized tool/LLM sanitizer (when applicable) -> mark or scope event
sanitizer -> subscriber dispatch

## Core Steps

Expand Down Expand Up @@ -88,6 +93,7 @@ Follow the `add-binding-feature` skill for the cross-binding implementation chec
- [ ] Callback error propagation
- [ ] Scope-local registration, inheritance, and cleanup on pop
- [ ] Event input/output semantics after middleware mutation
- [ ] Mark and scope event field semantics, including immutable identity fields
- [ ] Parity coverage in every affected binding

## Key References
Expand Down
4 changes: 3 additions & 1 deletion crates/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ should install it from npm rather than depend on the Rust crate directly.
napi-rs native extension.
- **Managed tool and LLM execution**: Helpers that emit lifecycle events and
run middleware in a consistent order.
- **Middleware APIs**: Guardrails and intercepts for tool and LLM boundaries.
- **Middleware APIs**: Guardrails and intercepts for tool and LLM boundaries,
plus mark and scope event sanitizers for `data`, `categoryProfile`, and
`metadata`.
- **Observability exporters**: Subscriber and exporter support for common
runtime telemetry flows.
- **Additional entry points**: `nemo-relay-node/typed`,
Expand Down
6 changes: 6 additions & 0 deletions docs/about-nemo-relay/concepts/events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ exporters. Consumers should prefer typed event fields, binding helpers, and
annotated request or response data before falling back to raw payload
re-reading.

Before that handoff, event sanitizers can replace `data`, `category_profile`,
and `metadata`. The runtime keeps identity, lifecycle, category, timestamp,
and parent fields immutable. Refer to
[Event Sanitizers](/reference/event-sanitizers) for registration levels and
ordering.

Events preserve runtime facts. They do not decide which scope owns a call, how
replay or cost policy is applied, how redaction policy is configured, how
streams are blocked, or how exporter-specific semantic projection works. Those
Expand Down
22 changes: 21 additions & 1 deletion docs/about-nemo-relay/concepts/middleware.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { MermaidStyles } from "@/components/MermaidStyles";
{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0 */}

This page explains the runtime behavior that runs around managed tool and LLM calls.
This page explains the runtime behavior that runs around managed tool and LLM
calls and sanitizes emitted mark and scope events.

## What Middleware Is

Expand Down Expand Up @@ -106,6 +107,22 @@ Sanitize-response guardrails rewrite the payload recorded on emitted end events.

Use them when the event stream should hide or reduce sensitive response data.

### Sanitize Mark and Scope Events

Event sanitizers cover observability fields that are outside the specialized
tool and LLM payload APIs. Separate registries apply to marks, scope starts,
and scope ends. They can rewrite `data`, `category_profile`, and `metadata`
while receiving the complete event as immutable context.

Scope event sanitizers run for every category. On tool and LLM scope events,
they run after the specialized request or response sanitizer. Mark sanitizers
cover explicit marks and marks materialized by middleware, plugins, and
streaming lifecycle helpers.

Register event sanitizers globally, on an owning scope, or through a plugin
context. For the callback contract and binding APIs, refer to
[Event Sanitizers](/reference/event-sanitizers).

Sanitize guardrails are observability-oriented. They do not rewrite the real
arguments passed to the callback or the real value returned to the caller.

Expand Down Expand Up @@ -261,6 +278,9 @@ Use these comparisons to pick the middleware surface that matches the behavior y
boundary.
- Use a **sanitize guardrail** when only subscribers and exporters should see
rewritten data.
- Use a **mark or scope event sanitizer** when the sensitive fields are in
`data`, `category_profile`, or `metadata` rather than the managed tool or LLM
request/response payload.
- Use a **stream execution intercept** when you need streaming-specific
behavior applied across the lifecycle of a long-lived or chunked response,
such as per-chunk transformation, incremental authorization, logging or
Expand Down
2 changes: 2 additions & 0 deletions docs/build-plugins/advanced-configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ Prefer observe-only defaults for new policies and execution-affecting intercepts
Use `PluginContext` to register:

- Subscribers
- Mark event sanitizers
- Scope-start and scope-end event sanitizers
- Tool guardrails
- Tool request and execution intercepts
- LLM guardrails
Expand Down
2 changes: 2 additions & 0 deletions docs/build-plugins/basic-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Keep runtime objects out of config. Provider clients, callbacks, file handles, c
A plugin can install one or more of these runtime surfaces:

- Subscribers
- Mark event sanitizers
- Scope-start and scope-end event sanitizers
- Tool sanitize-request guardrails
- Tool sanitize-response guardrails
- Tool conditional-execution guardrails
Expand Down
1 change: 1 addition & 0 deletions docs/build-plugins/code-examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ For example, a policy bundle can install:
- LLM request intercepts for request metadata
- Tool guardrails for policy enforcement
- Sanitize guardrails for exported payloads
- Mark and scope event sanitizers for non-request/response observability fields
- Shared component-local state used by those hooks

Use this pattern when the configured behavior is easier to reason about as one component than as several unrelated plugin components. Keep each registered surface small and make the component config explicit about which surfaces are enabled.
Expand Down
8 changes: 8 additions & 0 deletions docs/build-plugins/register-behavior.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ That gives the plugin system three important guarantees:

Use the context only after validation succeeds. Validation should inspect config and return diagnostics; registration should create runtime objects and attach them to the context.

The context includes mark, scope-start, and scope-end event sanitizer
registrations in addition to the tool and LLM middleware surfaces. Event
sanitizer callbacks receive the immutable event plus `data`,
`category_profile`, and `metadata`, and return only those observability fields.
Use the context methods so component name qualification and rollback also apply
to these registries. Refer to [Event Sanitizers](/reference/event-sanitizers)
for the binding-specific method names.

## Header Plugin Example

The same model applies in every binding: validate component-local config, then install middleware through the component-scoped registration context.
Expand Down
4 changes: 3 additions & 1 deletion docs/instrument-applications/adding-scopes-and-marks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@ Use this checklist before running the pattern in production traffic.
- Keep scope names stable enough for filtering and dashboards.
- Use marks for business checkpoints, not verbose debug logging.
- Include only JSON-compatible `data`, `metadata`, `input`, and `output` payloads.
- Do not attach sensitive payloads unless sanitize guardrails or exporter filters will remove them.
- Do not attach sensitive payloads unless a mark or scope
[event sanitizer](/reference/event-sanitizers) removes them before subscriber
and exporter delivery.
- Propagate the active scope stack when work crosses thread or worker boundaries.

## Next Steps
Expand Down
15 changes: 11 additions & 4 deletions docs/pii-redaction-plugin/about.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ position: 1
SPDX-License-Identifier: Apache-2.0 */}

Use the PII redaction plugin when you want first-party privacy controls for
managed NeMo Relay LLM and tool observability surfaces through the shared
plugin system.
managed NeMo Relay LLM, tool, mark, and generic scope observability surfaces
through the shared plugin system.

The built-in plugin component uses `kind = "pii_redaction"` and is available as
a first-party NeMo Relay plugin.
Expand All @@ -25,7 +25,8 @@ The plugin is designed around backend modes:

Start here when you need to:

- Remove sensitive fields from emitted tool or LLM payloads.
- Remove sensitive fields from emitted tool, LLM, mark, or generic scope
payloads.
- Replace sensitive text with a deterministic marker such as `[REDACTED]`.
- Hash matching values before observability exporters or subscribers receive
them.
Expand Down Expand Up @@ -61,10 +62,11 @@ The difference between middleware and `pii_redaction` is that middleware is the

## Current Scope

The built-in plugin currently exposes four managed sanitize surfaces:
The built-in plugin exposes five managed sanitize surfaces:

- `input`
- `output`
- `mark`
- `tool_input`
- `tool_output`

Expand All @@ -83,6 +85,11 @@ The current backend boundary is intentional:
- Managed LLM surfaces use the selected built-in codec so redaction can target
normalized Relay request and response shapes such as `/messages/0/content`
and `/message`.
- `mark` sanitizes `data`, `category_profile`, and `metadata` independently on
every mark event. It defaults to `true`; set `mark = false` to opt out.
- `input` and `output` also sanitize those three fields on non-tool, non-LLM
scope starts and ends. Tool and LLM scope envelopes remain on their
specialized sanitizer paths to avoid applying the same mask or hash twice.

## Observability Boundary

Expand Down
29 changes: 26 additions & 3 deletions docs/pii-redaction-plugin/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ The top-level PII redaction object contains:
|---|---|
| `version` | PII redaction config schema version. Defaults to `1`. |
| `mode` | Backend mode. Current values are `builtin` and `local_model`. |
| `input` | Enables managed LLM request sanitization. |
| `output` | Enables managed LLM response sanitization. |
| `input` | Enables managed LLM request sanitization and non-tool/LLM scope-start field sanitization. |
| `output` | Enables managed LLM response sanitization and non-tool/LLM scope-end field sanitization. |
| `mark` | Enables mark `data`, `category_profile`, and `metadata` sanitization. Defaults to `true`. |
| `tool_input` | Enables sanitization of emitted tool-request observability payloads. |
| `tool_output` | Enables sanitization of emitted tool-response observability payloads. |
| `priority` | Guardrail priority. Lower values run earlier. |
Expand All @@ -80,6 +81,7 @@ At least one managed redaction surface must be enabled.
| Built-in component kind and config validation | Supported | Supported |
| Managed LLM `input` | Supported | Not implemented |
| Managed LLM `output` | Supported | Not implemented |
| Mark and generic scope event fields | Supported | Not implemented |
| Managed `tool_input` | Supported | Not implemented |
| Managed `tool_output` | Supported | Not implemented |
| Built-in actions | `remove`, `redact`, `regex_replace`, `hash`, `mask` | N/A |
Expand Down Expand Up @@ -124,6 +126,7 @@ mode = "builtin"
codec = "openai_chat"
input = true
output = true
mark = true
tool_input = true
tool_output = true

Expand Down Expand Up @@ -151,6 +154,12 @@ This example configures the built-in backend for:
- LLM response redaction from the normalized response path `/message`
- tool argument redaction at `/api_key`
- tool result redaction at `/result/secret`
- mark and non-tool/LLM scope event redaction across `data`,
`category_profile`, and `metadata`

`mark` is enabled when omitted. Set `mark = false` only when marks are known to
contain no sensitive observability fields or another sanitizer owns that
boundary.

### CLI Editor Support

Expand All @@ -161,6 +170,7 @@ Use the editor when you want to:

- Toggle the component on or off
- Choose `builtin` or `local_model`
- Enable or opt out of mark sanitization
- Set the LLM `codec`
- Edit `builtin` action settings such as `action`, `target_paths`,
`pattern`, `detector`, `replacement`, and masking fields
Expand Down Expand Up @@ -270,6 +280,10 @@ The plugin uses different payload boundaries for tools and LLMs:
- LLMs use the selected built-in codec. Prefer normalized Relay paths such as:
- `/messages/0/content` for request message content
- `/message` for the normalized assistant response text
- Marks and non-tool/LLM scopes sanitize `data`, `category_profile`, and
`metadata` as separate JSON values. A target path is evaluated independently
inside each value; it does not start with `/data`, `/category_profile`, or
`/metadata`.

The current implementation also preserves provider-shaped response-path
compatibility for the supported codecs, but normalized LLM paths are the
Expand Down Expand Up @@ -333,9 +347,18 @@ The built-in plugin uses sanitize guardrails.
That means:

- the real provider response value is unchanged
- the emitted NeMo Relay start or end event payload is sanitized
- the emitted NeMo Relay start, end, or mark event observability fields are
sanitized
- `annotated_response` is populated from the sanitized end-event payload when a
response codec is provided
- subscribers, ATOF, ATIF, OpenTelemetry, and OpenInference receive the
sanitized event; raw values removed by the plugin do not bypass through an
exporter

For tool and LLM scope events, the generic scope sanitizer leaves the shared
fields unchanged because the specialized tool or LLM sanitizer already owns
that lifecycle boundary. This prevents repeated hashing or masking. Generic
scope categories use `input` for starts and `output` for ends.

## Local Model Mode

Expand Down
Loading
Loading