Skip to content

feat(plugin): add opt-in Rampart PII redaction - #558

Draft
afourniernv wants to merge 99 commits into
NVIDIA:mainfrom
afourniernv:feat/pii-worker-provider
Draft

feat(plugin): add opt-in Rampart PII redaction#558
afourniernv wants to merge 99 commits into
NVIDIA:mainfrom
afourniernv:feat/pii-worker-provider

Conversation

@afourniernv

@afourniernv afourniernv commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Overview

Add pii_rampart as an opt-in, in-process native Rust plugin. The plugin runs the pinned nationaldesignstudio/rampart ONNX model through Tract and sanitizes copied observability payloads without modifying provider or tool inputs and outputs.

Rampart is not linked into the Relay CLI, language bindings, or default host artifacts. The existing deterministic pii_redaction component remains unchanged and autoloaded as before. Users who do not install and configure this native plugin do not carry Tract or the model runtime.

  • I confirm this contribution is my own work, or I have the right to submit it under this project's license.
  • I searched existing issues and open pull requests, and this does not duplicate existing work.

Details

  • Add a standalone cdylib under plugins/pii-rampart with its own manifest, JSON Schema, lockfile, README, tests, and plugin lifecycle.
  • Keep the model files outside Relay. Activation requires an absolute local snapshot path and verifies the pinned graph, tokenizer, vocabulary, and configuration hashes before loading anything.
  • Implement the pinned tokenizer and structured prefilter in Rust, preserve UTF-8 offsets, validate model output, and apply explicit score, label, selector, replacement, and payload policies.
  • Use native ABI v3 completion-based async middleware for request and response sanitization. Callback scheduling and timeouts run on a plugin-owned Tokio runtime; CPU inference runs on a dedicated bounded Rayon pool rather than Relay's Tokio workers or shared blocking pool.
  • Bound admission to 16 callbacks, inference to three workers, and worker wait time to 500 ms. Cancellation, panic, timeout, malformed output, unsupported codec projection, and overload paths resolve through the existing fail-closed observability behavior.
  • Support the current built-in OpenAI, Anthropic, and Gemini codec identities plus the provider-native trajectory preset. The raw async ABI does not expose opaque host codec capabilities, so unsupported runtime codec identities fail closed rather than risking incorrect reconstruction.
  • Keep tract-onnx, Rayon, and all Rampart code behind the nemo-relay-pii-redaction/rampart feature used only by the standalone plugin.
  • Do not publish a Rust crate or bundle the model. Cross-platform plugin archives and GitHub release integration are intentionally separated into a follow-up PR ci(plugin): publish Rampart native plugin artifacts #737.

Validation on current main:

  • uv run pre-commit run --all-files
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo clippy --locked --manifest-path plugins/pii-rampart/Cargo.toml --all-targets -- -D warnings
  • just test-rust (full clean rerun passed)
  • cargo test -p nemo-relay-pii-redaction --features rampart (180 passed)
  • cargo test --locked --manifest-path plugins/pii-rampart/Cargo.toml
  • Release build and dynamic-load smoke using the exact pinned, hash-verified Q4 model snapshot

The real-model dynamic-load test exercises the built cdylib through PluginHostActivation, managed tool, OpenAI, and Gemini calls. It verifies that application payloads remain unchanged, observable payloads are redacted without safe-body omission, eight concurrent calls complete, cancellation resolves, and plugin teardown completes within two seconds.

The default Relay CLI dependency graph does not contain tract-onnx. The standalone unstripped macOS plugin is approximately 28.5 MB (approximately 23.0 MB after strip -x); that cost is paid only when the plugin artifact is installed.

Where should the reviewer start?

Start with plugins/pii-rampart/src/lib.rs for the native plugin and async completion lifecycle, then crates/pii-redaction/src/rampart/sanitizer.rs for bounded scheduling and fail-closed behavior. plugins/pii-rampart/tests/native_plugin.rs is the end-to-end dynamic-load test.

The main design decision is the distribution boundary: model inference stays inside the Relay Rust process, as requested, while the optional native plugin keeps Tract out of every default Relay artifact. The follow-up PR #737 handles platform-specific archives and release publication without widening this runtime PR.

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

@copy-pr-bot

copy-pr-bot Bot commented Jul 26, 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 Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 2e15c3d0-53e9-433b-882f-d6bbc94b6c13

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Changes

Rampart PII support adds an ONNX detector, structured prefilter, bounded asynchronous sanitizer, plugin registration, CLI editor integration, and Node, Go, and Python APIs. Host initialization paths register the component and expose configuration validation helpers.

Rampart PII plugin

Layer / File(s) Summary
Detector and plugin contract
crates/pii-redaction/src/rampart/*, crates/pii-redaction/Cargo.toml
Adds configuration validation, tokenization, structured PII prefiltering, verified model loading, and ONNX inference.
Sanitizer callbacks
crates/pii-redaction/src/rampart/sanitizer.rs, crates/pii-redaction/src/builtin.rs
Adds bounded executor admission, codec-aware redaction, fail-closed behavior, and asynchronous sanitizer callbacks.
Host and CLI integration
crates/ffi/src/api/plugin.rs, crates/python/src/lib.rs, crates/node/src/api/mod.rs, crates/cli/src/server/mod.rs, crates/cli/src/plugins/*
Registers Rampart during host initialization and adds CLI editing, persistence, summaries, schema handling, and registration errors.
Language APIs
crates/node/pii_rampart.*, go/nemo_relay/pii_rampart*, python/nemo_relay/pii_rampart.*, python/nemo_relay/__init__.*
Adds configuration types, component builders, metadata constants, validation helpers, package exports, and type declarations.
Validation and supporting updates
crates/node/tests/pii_rampart_tests.mjs, go/nemo_relay/pii_rampart*_test.go, python/tests/test_pii_rampart_plugin.py, crates/cli/tests/coverage/shared/plugins_tests.rs, Cargo.toml, ATTRIBUTIONS-Rust.md, .gitattributes
Adds cross-language and CLI coverage, enables the Rampart feature, refreshes attribution metadata, and marks generated files.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Host
  participant PluginRegistry
  participant RampartDetector
  participant RampartSanitizer
  participant Guardrail
  Host->>PluginRegistry: register Rampart PII component
  PluginRegistry->>RampartDetector: verify and load model artifacts
  PluginRegistry->>RampartSanitizer: create sanitizer
  PluginRegistry->>Guardrail: register surface callbacks
  Guardrail->>RampartSanitizer: sanitize selected payload
  RampartSanitizer->>RampartDetector: detect text batch
  RampartDetector-->>RampartSanitizer: return scored spans
  RampartSanitizer-->>Guardrail: return redacted payload
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.23% 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
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.
Title check ✅ Passed The title uses valid Conventional Commits syntax, has a lowercase scope, states the main change, and is under 72 characters.
Description check ✅ Passed The description includes all required sections, completed confirmations, change details, reviewer guidance, and a valid related issue reference.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@github-actions github-actions Bot added size:XXL PR is very large Feature a new feature lang:go PR changes/introduces Go code lang:js PR changes/introduces Javascript/Typescript code lang:python PR changes/introduces Python code lang:rust PR changes/introduces Rust code labels Jul 26, 2026
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@afourniernv
afourniernv force-pushed the feat/pii-worker-provider branch from b73ac66 to 385c241 Compare July 27, 2026 00:03
@willkill07 willkill07 added this to the 0.7 milestone Jul 27, 2026
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@afourniernv afourniernv changed the title feat: add worker-backed PII local models feat: add worker-backed PII detection Jul 27, 2026
@afourniernv
afourniernv marked this pull request as ready for review July 27, 2026 18:39
@afourniernv
afourniernv requested review from a team as code owners July 27, 2026 18:39
@afourniernv afourniernv changed the title feat: add worker-backed PII detection feat: run local PII models through gRPC workers Jul 27, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (1)
crates/worker/src/lib.rs (1)

676-705: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Merge push_registration into push_contract_registration to remove duplication.

Both functions build an identical Registration except for the contract field. Python's SDK already unifies this via a single _push_registration(..., *, contract: str = "") (see python/plugin/src/nemo_relay_plugin/_api.py lines 1288-1310); the Rust SDK diverging into two near-duplicate helpers risks future drift (e.g., a new field added to one but not the other).

♻️ Proposed refactor
-    fn push_registration(
-        &mut self,
-        name: &str,
-        surface: RegistrationSurface,
-        priority: i32,
-        break_chain: bool,
-    ) {
-        self.handlers.registrations.push(Registration {
-            local_name: name.into(),
-            surface: surface as i32,
-            priority,
-            break_chain,
-            contract: String::new(),
-        });
-    }
-
-    fn push_contract_registration(
-        &mut self,
-        name: &str,
-        surface: RegistrationSurface,
-        contract: &str,
-    ) {
-        self.handlers.registrations.push(Registration {
-            local_name: name.into(),
-            surface: surface as i32,
-            priority: 0,
-            break_chain: false,
-            contract: contract.into(),
-        });
-    }
+    fn push_registration(
+        &mut self,
+        name: &str,
+        surface: RegistrationSurface,
+        priority: i32,
+        break_chain: bool,
+    ) {
+        self.push_contract_registration(name, surface, priority, break_chain, "");
+    }
+
+    fn push_contract_registration(
+        &mut self,
+        name: &str,
+        surface: RegistrationSurface,
+        priority: i32,
+        break_chain: bool,
+        contract: &str,
+    ) {
+        self.handlers.registrations.push(Registration {
+            local_name: name.into(),
+            surface: surface as i32,
+            priority,
+            break_chain,
+            contract: contract.into(),
+        });
+    }

Then update the call in register_worker_inference to self.push_contract_registration(name, RegistrationSurface::WorkerInference, 0, false, contract);.

🤖 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 `@crates/worker/src/lib.rs` around lines 676 - 705, Merge push_registration
into push_contract_registration by giving push_contract_registration priority
and break_chain parameters, with contract supplied as the final argument and
defaulting to an empty string where appropriate. Remove the duplicate helper,
update all callers including register_worker_inference to pass the unified
arguments, and preserve existing Registration field values.
🤖 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 `@crates/pii-redaction/src/component.rs`:
- Around line 960-962: Update is_valid_json_pointer_pattern to validate wildcard
segments according to JsonPointerPattern::matches: reject any path segment that
contains '*' unless the entire segment is exactly "*". Preserve existing JSON
Pointer validation for all other segments and continue accepting standalone
wildcard segments.

In `@crates/pii-redaction/src/local.rs`:
- Around line 732-740: The empty-paths branch in llm_sanitize_request_callback
must sanitize request headers as well as request.content, preserving the
intended pointer prefixes for the headers and content roots. Reuse the existing
request/header sanitization behavior used by sanitize_raw_request or the builtin
flow, and ensure the broad-coverage path returns a request with sanitized header
values.

In `@crates/pii-redaction/tests/unit/component_tests.rs`:
- Around line 1916-1921: Update the table-driven assertion in the
validate_plugin_config test loop to include failure context identifying the
current config/field/message case and the produced diagnostics. Preserve the
existing matching condition while supplying a descriptive assertion message so
failures reveal which case failed and the actual report contents.

In `@crates/pii-redaction/tests/worker_detection_tests.rs`:
- Around line 54-58: Remove the duplicate “/message” selector from either
target_paths or target_path_patterns in the test configuration, keeping it in
only one collection so the test expresses a single intent.
- Around line 268-285: Update the fail-closed assertion in the worker exit test
around the event emitted by “worker-pii-exit” to use a message value that the
healthy fixture worker does not redact, while retaining the expected redaction
for “unselected” if applicable. Ensure the assertion can only pass when the
crashed batch is handled fail-closed, rather than matching normal “PRIVATE”
detection behavior.

In `@crates/pii-redaction/workers/rampart/README.md`:
- Around line 134-136: Update the Runtime Bounds section in the README by adding
a complete introductory sentence before the existing bullet list; leave the
documented limits unchanged and ensure the lead-in grammatically introduces the
list.

In `@docs/build-plugins/dynamic-plugins/grpc-worker/python/about.mdx`:
- Line 168: Rename the “Register worker inference” heading to “Register Worker
Inference” in docs/build-plugins/dynamic-plugins/grpc-worker/python/about.mdx at
lines 168-168 and docs/build-plugins/dynamic-plugins/grpc-worker/rust/about.mdx
at lines 71-71, preserving the existing heading structure.

In `@docs/configure-plugins/pii-redaction/configuration.mdx`:
- Around line 424-431: The omission guidance following the “Path Semantics”
section is outdated. Update the paragraph describing manual LLM calls with
normalized target_paths and no active or fallback codec to state that payloads
are sanitized using the configured raw paths and emitted, matching the
early-return behavior in builtin and local redaction flows; preserve the
documented fail-closed contract.
- Around line 329-331: Add a complete introductory sentence immediately before
the TOML code block following the sanitizer registration-rejection paragraph,
clearly describing what the configuration example demonstrates. Keep the
existing TOML content unchanged.

In `@go/nemo_relay/pii_redaction/pii_redaction_test.go`:
- Around line 47-62: Extend the validation condition in the NewComponentSpec
test to assert that spec.Config.Local.Backend matches the configured backend
value from the test setup. Keep the existing configuration assertions unchanged
and include the backend check alongside the other Local fields.

In `@python/plugin/README.md`:
- Around line 108-125: Update the Worker Inference example to establish that ctx
is a PluginContext available inside WorkerPlugin.register, either by showing the
enclosing register method or explicitly stating that scope. Keep the
register_worker_inference usage and handler behavior unchanged.

---

Outside diff comments:
In `@crates/worker/src/lib.rs`:
- Around line 676-705: Merge push_registration into push_contract_registration
by giving push_contract_registration priority and break_chain parameters, with
contract supplied as the final argument and defaulting to an empty string where
appropriate. Remove the duplicate helper, update all callers including
register_worker_inference to pass the unified arguments, and preserve existing
Registration field values.
🪄 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: ASSERTIVE

Plan: Enterprise

Run ID: b6f7fc1b-5518-49b8-af16-dd1759b6fa64

📥 Commits

Reviewing files that changed from the base of the PR and between 42c7655 and 7541bec.

📒 Files selected for processing (60)
  • crates/cli/src/server/mod.rs
  • crates/core/src/lib.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/host.rs
  • crates/core/src/plugin/dynamic/worker.rs
  • crates/core/src/plugin/worker_inference.rs
  • crates/core/tests/fixtures/worker_plugin/src/main.rs
  • crates/core/tests/integration/worker_plugin_tests.rs
  • crates/core/tests/unit/dynamic_worker_tests.rs
  • crates/core/tests/unit/plugin_tests.rs
  • crates/core/tests/unit/worker_inference_tests.rs
  • crates/node/pii_redaction.d.ts
  • crates/node/pii_redaction.js
  • crates/node/tests/pii_redaction_tests.mjs
  • crates/pii-redaction/Cargo.toml
  • crates/pii-redaction/README.md
  • crates/pii-redaction/src/builtin.rs
  • crates/pii-redaction/src/component.rs
  • crates/pii-redaction/src/local.rs
  • crates/pii-redaction/tests/unit/component_tests.rs
  • crates/pii-redaction/tests/unit/local_tests.rs
  • crates/pii-redaction/tests/worker_detection_tests.rs
  • crates/pii-redaction/workers/rampart/MANIFEST.in
  • crates/pii-redaction/workers/rampart/README.md
  • crates/pii-redaction/workers/rampart/THIRD_PARTY_NOTICES.md
  • crates/pii-redaction/workers/rampart/config.schema.json
  • crates/pii-redaction/workers/rampart/nemo_relay_pii_rampart/__init__.py
  • crates/pii-redaction/workers/rampart/nemo_relay_pii_rampart/detector.py
  • crates/pii-redaction/workers/rampart/nemo_relay_pii_rampart/prefetch.py
  • crates/pii-redaction/workers/rampart/nemo_relay_pii_rampart/py.typed
  • crates/pii-redaction/workers/rampart/nemo_relay_pii_rampart/worker.py
  • crates/pii-redaction/workers/rampart/pyproject.toml
  • crates/pii-redaction/workers/rampart/relay-plugin.toml
  • crates/pii-redaction/workers/rampart/tests/test_detector.py
  • crates/pii-redaction/workers/rampart/tests/test_worker.py
  • crates/worker-proto/README.md
  • crates/worker-proto/proto/nemo/relay/worker/v1/plugin_worker.proto
  • crates/worker-proto/tests/proto_tests.rs
  • crates/worker/README.md
  • crates/worker/src/lib.rs
  • crates/worker/tests/worker_sdk_tests.rs
  • docs/about-nemo-relay/release-notes/index.mdx
  • docs/build-plugins/dynamic-plugins/grpc-worker/grpc-worker-protocol.mdx
  • docs/build-plugins/dynamic-plugins/grpc-worker/python/about.mdx
  • docs/build-plugins/dynamic-plugins/grpc-worker/rust/about.mdx
  • docs/configure-plugins/pii-redaction/about.mdx
  • docs/configure-plugins/pii-redaction/configuration.mdx
  • go/nemo_relay/pii_redaction.go
  • go/nemo_relay/pii_redaction/pii_redaction.go
  • go/nemo_relay/pii_redaction/pii_redaction_test.go
  • go/nemo_relay/pii_redaction_test.go
  • justfile
  • python/nemo_relay/pii_redaction.py
  • python/nemo_relay/pii_redaction.pyi
  • python/plugin/README.md
  • python/plugin/src/nemo_relay_plugin/__init__.py
  • python/plugin/src/nemo_relay_plugin/_api.py
  • python/tests/plugin/test_public_api_docstrings.py
  • python/tests/plugin/test_worker_sdk.py
  • python/tests/test_pii_redaction_plugin.py

Comment thread crates/pii-redaction/src/component.rs Outdated
Comment thread crates/pii-redaction/src/local.rs Outdated
Comment thread crates/pii-redaction/tests/unit/component_tests.rs Outdated
Comment thread crates/pii-redaction/tests/worker_detection_tests.rs Outdated
Comment thread crates/pii-redaction/tests/worker_detection_tests.rs Outdated
Comment thread docs/build-plugins/dynamic-plugins/grpc-worker/python/about.mdx Outdated
Comment thread docs/configure-plugins/pii-redaction/configuration.mdx Outdated
Comment thread docs/configure-plugins/pii-redaction/configuration.mdx Outdated
Comment thread go/nemo_relay/pii_redaction/pii_redaction_test.go Outdated
Comment thread python/plugin/README.md Outdated
Signed-off-by: Alex Fournier <afournier@nvidia.com>

@ericevans-nv ericevans-nv 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.

You could refine local_model to resolve an integration-provided callback. The PII middleware would execute that callback with the selected text and detector settings, then receive the detected spans, labels, and confidence scores. The integration could implement the callback using any model, runtime, or transport it chooses, while the PII component continues to own field selection, detection validation, policy, and redaction.

Signed-off-by: Alex Fournier <afournier@nvidia.com>
@afourniernv

afourniernv commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Current-head production benchmark

Reran the Rampart validation on commit be1725e4 using a release build on an Apple M4 Pro (14 logical CPUs, 48 GiB RAM) and the pinned, hash-verified Q4 model snapshot. The harness and model files remained temporary and are not part of the branch.

This head adds a bounded, content-addressed decision cache and field-level model-budget fallback:

  • Cache keys are SHA-256 digests. Cached values contain only keep/redact/fail-closed decisions and byte ranges, never selected or sanitized text.
  • The cache is bounded to 4,096 entries and 4 MiB of decision ranges per plugin activation.
  • Exact strings are deduplicated within a payload and across later calls. Cache hits do not consume the per-payload unique-text or model-window budget.
  • If a group exceeds the model budget, Rampart splits it until each field can be evaluated. An individually oversized field fails closed without removing the surrounding request, response, tool payload, or event.
  • Transient model/output failures are not cached. Budget-driven field omission is warning-logged without field content.

Real Claude Code and Codex traffic

I rebuilt the actual Relay CLI and ran cold direct calls plus tool-heavy sessions through Anthropic Messages and OpenAI Responses at the default four-window budget.

  • 15/15 LLM starts and 15/15 LLM ends retained their observable bodies: 4/4 Anthropic and 11/11 OpenAI Responses on each side.
  • 36/36 tool scopes retained their observable bodies across Bash, Glob, Grep, and Read.
  • All 37 lifecycle UUID groups were structurally valid.
  • Zero synthetic privacy-canary leaks were present in 1,263 emitted events.
  • Cold Codex instructions between 8.2 and 17.8 KiB exceeded the individual four-window budget and were replaced field-by-field; the request envelopes and all other fields remained available.

For comparison, the equivalent pre-cache harness retained only 1/5 Anthropic request bodies and 0/11 OpenAI Responses request bodies even with a 16-window budget.

Synthetic production matrix

The 16-window run gives the direct comparison with the prior benchmark.

Workload Throughput p50 p95 p99
Repeated sequential request/response 3,538.92 calls/s 0.275 ms 0.361 ms 0.372 ms
Eight-way agent fan-out with 5 ms provider delay 1,243.78 calls/s 6.228 ms 6.798 ms 6.926 ms
1,200-call soak at concurrency 8 5,952.93 calls/s 1.206 ms 1.737 ms 2.225 ms

The soak retained all 2,400 LLM bodies with zero canary leaks and zero fail-closed bodies. Tokio heartbeat p99 was 0.449 ms, heartbeat max was 0.901 ms, and max RSS was 231.3 MB.

Repeated-field timings separate the first cold inference from later exact cache hits:

Selected field Cold Warm p50
64 B 4.346 ms 0.213 ms
256 B 10.560 ms 0.198 ms
1 KiB 42.322 ms 0.226 ms
8 KiB 446.440 ms 0.564 ms
16 KiB 924.273 ms 0.304 ms

At the default four-window budget, the 8 KiB field took 434.048 ms cold. The 16 KiB field was rejected field-by-field in 1.506 ms rather than spending second-scale inference time or removing the full envelope.

Redaction quality stayed unchanged on 210 balanced AI4Privacy rows across seven languages:

  • 1,124/1,128 modeled sensitive spans redacted: 99.645%.
  • Wilson 95% interval: 99.092%-99.862%.
  • 460/460 intentionally unmodeled public terms retained.
  • The same four misses remained: one SOCIALNUM, two CITY spans, and one questionable BUILDINGNUM annotation.

Validation on this head:

  • just test-rust
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo fmt --all
  • uv run pre-commit run --all-files
  • Focused Rampart suite: 160/160 passed

The practical limit remains explicit: a unique cold field that exceeds its configured model-window budget is replaced in observability. Repeated coding-agent instructions, tool schemas, and history no longer repeatedly consume that budget or cause the whole request body to disappear.

@afourniernv

Copy link
Copy Markdown
Contributor Author

/ok to test 1866014

@afourniernv
afourniernv marked this pull request as draft August 3, 2026 21:52
@afourniernv

Copy link
Copy Markdown
Contributor Author

/ok to test be1725e

Signed-off-by: Alex Fournier <afournier@nvidia.com>
@afourniernv

Copy link
Copy Markdown
Contributor Author

/ok to test 218b7eb

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

License Diff

Compared against origin/main.

Lockfile license changes

Lockfile License Changes

Rust

Added

  • adler2 2.0.1 (Apache-2.0)
  • anymap3 1.1.0 (Apache-2.0)
  • byteorder 1.5.0 (MIT)
  • crc32fast 1.5.0 (Apache-2.0)
  • crunchy 0.2.4 (MIT)
  • derive-new 0.7.0 (MIT)
  • downcast-rs 2.0.2 (Apache-2.0)
  • dyn-eq 0.1.3 (MPL-2.0)
  • dyn-hash 1.0.0 (Apache-2.0)
  • filetime 0.2.29 (Apache-2.0)
  • flate2 1.1.9 (Apache-2.0)
  • float-ord 0.3.2 (Apache-2.0)
  • half 2.7.1 (Apache-2.0)
  • inventory 0.3.24 (Apache-2.0)
  • libm 0.2.16 (MIT)
  • maplit 1.0.2 (Apache-2.0)
  • matrixmultiply 0.3.11 (Apache-2.0)
  • memmap2 0.9.11 (Apache-2.0)
  • memo-map 0.3.3 (Apache-2.0)
  • minijinja 2.21.0 (Apache-2.0)
  • miniz_oxide 0.8.9 (Apache-2.0)
  • ndarray 0.17.2 (Apache-2.0)
  • nom-language 0.1.0 (MIT)
  • pastey 0.2.3 (Apache-2.0)
  • portable-atomic-util 0.2.7 (Apache-2.0)
  • primal-check 0.3.4 (Apache-2.0)
  • rand_distr 0.6.0 (Apache-2.0)
  • rawpointer 0.2.1 (Apache-2.0)
  • rayon 1.12.0 (Apache-2.0)
  • rayon-core 1.13.0 (Apache-2.0)
  • rustfft 6.4.1 (Apache-2.0)
  • safetensors 0.8.0 (Apache-2.0)
  • scan_fmt 0.2.6 (MIT)
  • simd-adler32 0.3.10 (MIT)
  • strength_reduce 0.2.4 (Apache-2.0)
  • string-interner 0.20.0 (Apache-2.0)
  • tar 0.4.46 (Apache-2.0)
  • tract-core 0.23.4 (Apache-2.0)
  • tract-data 0.23.4 (Apache-2.0)
  • tract-extra 0.23.4 (Apache-2.0)
  • tract-hir 0.23.4 (Apache-2.0)
  • tract-linalg 0.23.4 (Apache-2.0)
  • tract-nnef 0.23.4 (Apache-2.0)
  • tract-onnx 0.23.4 (Apache-2.0)
  • tract-onnx-opl 0.23.4 (Apache-2.0)
  • tract-pulse 0.23.4 (Apache-2.0)
  • tract-pulse-opl 0.23.4 (Apache-2.0)
  • tract-transformers 0.23.4 (Apache-2.0)
  • transpose 0.2.3 (Apache-2.0)
  • unicode-normalization 0.1.25 (Apache-2.0)
  • unicode_categories 0.1.1 (Apache-2.0)
  • xattr 1.6.1 (Apache-2.0)

Removed

  • None

Updated/Changed

bit-set

Before:

  • bit-set 0.8.0 (Apache-2.0)

After:

  • bit-set 0.10.0 (Apache-2.0)
  • bit-set 0.8.0 (Apache-2.0)

bit-vec

Before:

  • bit-vec 0.8.0 (Apache-2.0)

After:

  • bit-vec 0.8.0 (Apache-2.0)
  • bit-vec 0.9.1 (Apache-2.0)

erased-serde

Before:

  • erased-serde 0.4.10 (MIT OR Apache-2.0)

After:

  • erased-serde 0.4.10 (Apache-2.0)

tinyvec

Before:

  • tinyvec 1.11.0 (Zlib OR Apache-2.0 OR MIT)

After:

  • tinyvec 1.11.0 (Apache-2.0)

tinyvec_macros

Before:

  • tinyvec_macros 0.1.1 (MIT OR Apache-2.0 OR Zlib)

After:

  • tinyvec_macros 0.1.1 (Apache-2.0)

typeid

Before:

  • typeid 1.0.3 (MIT OR Apache-2.0)

After:

  • typeid 1.0.3 (Apache-2.0)

zerocopy-derive

Before:

  • zerocopy-derive 0.8.48 (BSD-2-Clause OR Apache-2.0 OR MIT)

After:

  • zerocopy-derive 0.8.48 (Apache-2.0)

Node

Added

  • None

Removed

  • None

Updated/Changed

  • None

Python

Added

  • None

Removed

  • None

Updated/Changed

  • None
Status output
[license-diff] selected languages: rust, node, python
[license-diff] generating current inventory
[license-diff] current: generating Rust inventory
[license-diff] current: Rust inventory complete (503 packages)
[license-diff] current: generating Node inventory
[license-diff] current: Node inventory complete (367 packages)
[license-diff] current: generating Python inventory
[license-diff] current: Python inventory complete (105 packages)
[license-diff] current inventory complete
[license-diff] checking out base ref origin/main into a temporary worktree
[license-diff] base: generating Rust inventory
[license-diff] base: Rust inventory complete (449 packages)
[license-diff] base: generating Node inventory
[license-diff] base: Node inventory complete (367 packages)
[license-diff] base: generating Python inventory
[license-diff] base: Python inventory complete (105 packages)
[license-diff] base inventory complete
[license-diff] removing temporary base worktree
[license-diff] comparing inventories
[license-diff] rendering Markdown output
[license-diff] done

@github-actions github-actions Bot removed lang:go PR changes/introduces Go code lang:js PR changes/introduces Javascript/Typescript code lang:python PR changes/introduces Python code labels Aug 7, 2026
@afourniernv afourniernv changed the title feat: add in-process Rampart PII redaction plugin feat(plugin): add opt-in Rampart PII redaction Aug 7, 2026
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@github-actions github-actions Bot added the lang:python PR changes/introduces Python code label Aug 7, 2026
@copy-pr-bot

copy-pr-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

/ok to test c7c2f371d4a7f862d1062944219a678736672c3b

@afourniernv, there was an error processing your request: E2

See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/2/

Signed-off-by: Alex Fournier <afournier@nvidia.com>
@afourniernv afourniernv added the DO NOT MERGE PR should not be merged; see PR for details label Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DO NOT MERGE PR should not be merged; see PR for details Feature a new feature lang:python PR changes/introduces Python code lang:rust PR changes/introduces Rust code size:XXL PR is very large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants