Skip to content

docs: document LLM request and tool execution outcomes - #341

Merged
rapids-bot[bot] merged 4 commits into
NVIDIA:mainfrom
bbednarski9:docs/llm-intercept-pending-marks
Jul 2, 2026
Merged

docs: document LLM request and tool execution outcomes#341
rapids-bot[bot] merged 4 commits into
NVIDIA:mainfrom
bbednarski9:docs/llm-intercept-pending-marks

Conversation

@bbednarski9

@bbednarski9 bbednarski9 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Overview

Document the canonical LLM request-intercept and tool-execution intercept outcome contracts.

  • 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

  • Resolve reviewer feedback in the LLM request-intercept outcome reference with clearer purpose, lifecycle, diagram, and binding-contract explanations.
  • Add a parallel tool-execution outcome reference covering raw next(args) behavior, pending marks, end-before-mark lifecycle ordering, migration, and binding contracts.
  • Update the Python, Node.js, and Rust tool execution middleware examples to return the canonical outcome.
  • Keep this PR documentation-only; it contains no runtime changes.

Where should the reviewer start?

  • docs/reference/llm-request-intercept-outcomes.mdx
  • docs/reference/tool-execution-intercept-outcomes.mdx
  • docs/instrument-applications/advanced-guide.mdx

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

Validation

  • Generated Python, Node.js, and Rust API reference pages.
  • Fern check completed with zero errors; redirects validation was skipped because no Fern token is configured.
  • Fern strict broken-link validation passed.
  • git diff --check

Summary by CodeRabbit

  • Documentation
    • Added a new reference page defining the canonical format and lifecycle rules for request-intercept outcomes (including codec-aware validation and migration guidance).
    • Added a new reference page defining the canonical format and lifecycle rules for tool-execution intercept outcomes (including pending-mark handling and discard behavior).
  • Documentation
    • Updated request-intercept and middleware/tool-policy examples across Python, Rust, and Node.js to return structured outcome objects and to derive rewritten requests from captured outcomes.
    • Clarified codec-aware request authority and content/annotation responsibilities in the rewritten provider request flow.

Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 59026f8e-986f-468a-9c49-1c59b215b998

📥 Commits

Reviewing files that changed from the base of the PR and between 93f37b1 and eafb1c6.

📒 Files selected for processing (3)
  • docs/instrument-applications/advanced-guide.mdx
  • docs/instrument-applications/code-examples.mdx
  • docs/reference/llm-request-intercept-outcomes.mdx
💤 Files with no reviewable changes (3)
  • docs/instrument-applications/code-examples.mdx
  • docs/instrument-applications/advanced-guide.mdx
  • docs/reference/llm-request-intercept-outcomes.mdx
📜 Recent review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Preview docs

Walkthrough

Documentation standardizes LLM request-intercept and tool-execution intercept outcome contracts, updates code examples to use outcome objects, and adds reference pages for serialization, lifecycle, binding, and migration rules.

Changes

Intercept Outcome Documentation

Layer / File(s) Summary
Canonical LLM request-intercept reference
docs/reference/llm-request-intercept-outcomes.mdx
Defines the canonical outcome fields, request authority rules, intercept resolution flow, language/ABI mappings, lifecycle timing, and migration notes for LLM request intercepts.
Canonical tool-intercept reference
docs/reference/tool-execution-intercept-outcomes.mdx
Defines the canonical tool execution outcome shape, continuation semantics, lifecycle event ordering, binding mappings, and migration guidance.
Build-plugin and provider-codec outcome returns
docs/build-plugins/code-examples.mdx, docs/build-plugins/register-behavior.mdx, docs/integrate-into-frameworks/provider-codecs.mdx
Python and Rust add_header examples return outcome objects, and the provider-codec workflow and example use the same LLMRequestInterceptOutcome contract.
Request-intercept outcome usage in framework examples
docs/integrate-into-frameworks/code-examples.mdx
Python, Node.js, and Rust examples now capture intercept results as outcome and read outcome.request before downstream use.
Tool execution examples updated to outcome return type
docs/instrument-applications/advanced-guide.mdx
Python, Node.js, and Rust tool execution examples now wrap forwarded next results in outcome objects, and the guide text explains the return contract.

Estimated code review effort: 2 (Simple) | ~12 minutes

Suggested labels: DO NOT MERGE, duplicate

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits and accurately summarizes the documentation-only outcome contract updates.
Description check ✅ Passed The description matches the template with overview, details, review start, and related issues sections filled in.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 commented Jul 1, 2026

Copy link
Copy Markdown

@willkill07 willkill07 added this to the 0.5 milestone Jul 1, 2026
rapids-bot Bot pushed a commit that referenced this pull request Jul 1, 2026
#### Overview

Finalize one canonical LLM request-intercept outcome across the Rust runtime, built-in and adaptive plugins, native ABI v1, `grpc-v1` workers, public C FFI, Go, Python, Node.js, and WebAssembly.

Request intercepts can rewrite the provider request, carry an optional normalized annotation, and schedule ordered marks for the managed LLM lifecycle:

```json
{
  "request": {"headers": {}, "content": {}},
  "annotated_request": null,
  "pending_marks": []
}
```

`request` is required. `annotated_request` defaults to `null`, and `pending_marks` defaults to an empty list. Each pending mark contains only its name, optional category and category profile, data, and metadata; Relay continues to own event UUIDs, parent UUIDs, and timestamps.

The finalized contract also defines one provider-body source of truth. Without a request codec, `outcome.request.content` is authoritative. With a codec, `outcome.annotated_request` is required and authoritative, `outcome.request.content` is read-only context, and `outcome.request.headers` remains writable.

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

#### Why

Request intercepts run before Relay creates the managed LLM handle. A mark emitted directly from an intercept therefore cannot reliably attach to that future LLM scope. Returning pending mark specifications lets the lifecycle owner emit them at the correct boundary without leaking control data into provider requests, annotations, codecs, sanitizers, or execution intercepts.

Codec-aware interception also previously allowed two conflicting provider-body representations: an intercept could change both the raw request content and its normalized annotation, while Relay later encoded only the annotation. Making authority explicit prevents raw content edits from being silently discarded.

#### Details

- Make `LlmRequestInterceptOutcome` the only Rust callback result and keep one `register_llm_request_intercept` registration family for global, scope-local, plugin-context, and adaptive paths.
- Propagate each accepted request and annotation to the next intercept while appending pending marks in effective middleware order.
- Without a request codec, use `outcome.request.content` as the provider body.
- With a request codec, require `outcome.annotated_request`, encode the provider body from it, and allow header changes only through `outcome.request.headers`.
- Reject raw `request.content` mutations or missing annotations at the offending codec-path intercept, before later middleware, LLM lifecycle creation, mark emission, or provider invocation.
- Preserve marks from an intercept that breaks the chain; discard all accumulated marks if any intercept fails.
- Return the complete outcome from standalone request-intercept helpers. These helpers expose pending marks but do not emit them because they do not own an LLM lifecycle.
- After successful interception, create the LLM handle and capture one subscriber snapshot before emitting lifecycle events.
- Emit LLM start at `T`, every pending mark at `T + 1µs` in returned order with the LLM UUID as parent, and LLM end at or after `T + 1µs`.
- Apply the same behavior to streaming and non-streaming managed execution, including provider errors and stream finalization.
- Keep pending marks separate from provider-visible requests and annotations.

#### Boundary contracts

- **Native ABI v1:** return one host-owned outcome JSON string. Remove the private annotation-envelope transport and append required outcome-contract version fields to both host and plugin descriptor tables so stale binaries fail before callback invocation.
- **`grpc-v1`:** return one `JsonEnvelope` using schema `nemo.relay.LlmRequestInterceptOutcome@1`.
- **Public C FFI:** return one owned `char **out_outcome_json` and add `nemo_relay_llm_request_intercept_outcome_json_new`.
- **Go:** return `(LLMRequestInterceptOutcome, error)` and expose request, outcome, and pending-mark DTOs.
- **Python:** return `LLMRequestInterceptOutcome` and export `PendingMarkSpec`.
- **Node.js and WebAssembly:** return `{ request, annotated?, pendingMarks? }`. Binding-owned pending-mark DTOs use `categoryProfile`; canonical event and outcome JSON retains `category_profile`.
- **Rust native and worker SDKs:** expose only the canonical callback and registration method.

#### Breaking changes

This intentionally finalizes unpublished contracts in place:

- Rust and Python tuple results are removed.
- C and Go split outputs are removed.
- Mark-specific parallel registration variants are removed.
- The native annotation metadata envelope and fallback parser are removed.
- Native ABI host and plugin tables require the finalized outcome-contract field.
- The `grpc-v1` request-intercept result is replaced by the canonical outcome envelope.
- Codec-path intercepts must return an annotation and may no longer mutate raw `request.content`; malformed outcomes fail before lifecycle creation.
- Node.js and WebAssembly pending-mark objects use `categoryProfile` instead of the Rust/wire name `category_profile`.

All development native plugins and workers must rebuild against this version.

#### Where should the reviewer start?

1. `crates/types/src/api/event.rs` and `crates/types/src/api/llm.rs` for the canonical data contract.
2. `crates/core/src/api/runtime/state.rs`, `crates/core/src/api/shared.rs`, `crates/core/src/api/llm.rs`, and `crates/core/src/stream.rs` for chaining, codec authority, validation, and lifecycle behavior.
3. `crates/plugin/src/lib.rs`, `crates/core/src/plugin/dynamic/native.rs`, and `crates/core/src/plugin/dynamic/worker.rs` for native and worker boundaries.
4. `crates/ffi`, `go/nemo_relay`, `crates/python`, `crates/node`, and `crates/wasm` for binding contracts and DTO conversion.
5. `crates/core/tests/integration/middleware_tests.rs`, `crates/core/tests/integration/pipeline_tests.rs`, `crates/plugin/tests/typed_callbacks.rs`, and the binding tests for lifecycle, codec-authority, and boundary coverage.

The full contract, request-authority diagram, and migration notes are tracked in [companion documentation PR #341](#341), which should merge immediately after this PR.

#### Testing

- `cargo test --workspace --all-targets`
- `cargo clippy --workspace --all-targets -- -D warnings`
- `cargo fmt --all -- --check`
- Python codec and worker SDK coverage passes, including malformed codec-path outcomes and canonical worker envelopes.
- Node.js LLM suite: **38 passed**, including `categoryProfile` input/output conversion and codec-authority rejection.
- Go: all `go/nemo_relay/...` packages passed, including codec-authority coverage; `go vet ./...` passes.
- Native SDK: **52 passed**.
- Worker SDK: **9 passed**; worker protocol tests: **6 passed**.
- C FFI: unit and integration suites passed, including owned outcome allocation and malformed/null input coverage.
- WebAssembly native Rust tests: **13 passed**, including camelCase pending-mark DTO round trips and rejection of the wire-only `category_profile` spelling.
- Repository formatting, strict Clippy, Ruff, Prettier, type, lockfile, FFI-header, and applicable pre-commit checks pass.

`wasm-pack` and the `wasm32-unknown-unknown` Rust target were not available for the package-level Wasm suite. Environment-dependent socket and external-network tests were not used to validate these binding changes.

#### Related Issues

- Relates to #296



## Summary by CodeRabbit

* **New Features**
  * LLM request intercepts can now return a unified outcome that includes the rewritten request, optional annotated request, and pending marks.
  * Pending marks are now emitted alongside LLM lifecycle events and supported across SDKs and plugins.

* **Bug Fixes**
  * Improved consistency of LLM event timing and parent/child relationships.
  * Added stricter validation so intercepts that modify raw request content or omit required annotations are rejected when needed.

Authors:
  - Bryan Bednarski (https://github.com/bbednarski9)

Approvers:
  - Will Killian (https://github.com/willkill07)

URL: #327
@bbednarski9
bbednarski9 marked this pull request as ready for review July 1, 2026 17:59
@bbednarski9
bbednarski9 requested a review from lvojtku as a code owner July 1, 2026 17:59
@coderabbitai coderabbitai Bot added the DO NOT MERGE PR should not be merged; see PR for details label Jul 1, 2026
Comment thread docs/reference/llm-request-intercept-outcomes.mdx Outdated
Comment thread docs/reference/llm-request-intercept-outcomes.mdx Outdated
Comment thread docs/reference/llm-request-intercept-outcomes.mdx Outdated
Comment thread docs/reference/llm-request-intercept-outcomes.mdx
Comment thread docs/reference/llm-request-intercept-outcomes.mdx Outdated
Comment thread docs/reference/llm-request-intercept-outcomes.mdx Outdated
@willkill07 willkill07 removed the DO NOT MERGE PR should not be merged; see PR for details label Jul 1, 2026
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@bbednarski9 bbednarski9 changed the title docs: document LLM request intercept outcomes docs: document LLM request and tool execution outcomes Jul 2, 2026
@bbednarski9

bbednarski9 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

@lvojtku im doubling up this PR for easier review to include deltas for PR #327 and PR #350 as they follow the same pattern. Lets wait until #350 is merged to bring this in

rapids-bot Bot pushed a commit that referenced this pull request Jul 2, 2026
…350)

## Summary

- make `ToolExecutionInterceptOutcome` the canonical return type for every registered tool execution intercept
- keep the default tool callable and `next(args)` continuation as raw JSON while Relay retains downstream pending marks
- use the existing global, scope-local, and plugin registration paths without a mark-specific registration API
- emit ordered pending marks after the managed tool end event, parented to the Relay-owned tool UUID
- carry the same contract through native plugins, `grpc-v1` workers, C, Go, Python, and Node

## Motivation

Tool execution intercepts only learn the final result after execution, while Relay owns the managed tool lifecycle and its UUID. Returning a canonical outcome keeps plugin control data separate from the application-visible tool result and lets Relay materialize marks with correct parentage.

This follows the outcome-return convention already used by LLM request intercepts instead of introducing a second registration family or an implementation-specific callback variant.

## Behavior

- applications and sanitize-response guardrails receive only `outcome.result`
- successful calls emit the tool end event first, followed by pending marks in effective middleware order
- marks retain category, category profile, data, and metadata
- repeated concurrent `next(...)` calls preserve invocation order rather than completion order
- execution errors discard accumulated marks
- the subscriber snapshot taken at tool start is reused for the end event and pending marks
- legacy raw intercept results are rejected at public and dynamic-plugin boundaries

## Boundary contracts

- Rust and native plugins return `ToolExecutionInterceptOutcome`
- `grpc-v1` workers return a `ToolExecutionInterceptResult` containing the exact `nemo.relay.ToolExecutionInterceptOutcome@1` envelope
- C callbacks return JSON with `result` and optional `pending_marks`
- Go callbacks return `ToolExecutionInterceptOutcome`
- Python callbacks return `ToolExecutionInterceptOutcome`
- Node callbacks return `{ result, pendingMarks? }`

## Breaking changes

- every registered tool execution intercept must return the canonical outcome type
- the raw default tool callable and raw `next(args)` result are unchanged
- existing registration names are unchanged; there is no parallel outcome-specific registration path
- native plugins, workers, and language bindings must rebuild against current `main`

## Validation

- `cargo test --workspace --all-targets -- --test-threads=1`
- `cargo check --workspace --all-targets`
- `cargo clippy --workspace --all-targets -- -D warnings`
- `cargo fmt --all -- --check`
- native plugin SDK: 53 tests passed
- worker SDK: 11 tests passed
- Node: 246 tests passed
- Python: 496 tests passed, 39 optional-integration tests skipped
- Go: `go test ./...` and `go vet ./...`
- Ruff checks and formatting
- `git diff --check`

## Documentation

No documentation changes are included. Follow-up documentation remains in #341.



## Summary by CodeRabbit

* **New Features**
  * Tool execution intercepts now return a typed, structured outcome containing `result` plus optional `pendingMarks`, with consistent support across Rust, Node, Python, Go, and the worker SDK.
  * Pending marks are emitted and associated deterministically with the correct tool execution lifecycle events.
* **Bug Fixes**
  * Improved propagation/merging of downstream pending marks for pass-through, chaining, and global+scope-local flows.
  * Rejections for legacy, malformed, or nonconforming outcomes are clearer; event ordering/parent-child relationships are corrected.
* **Documentation**
  * Updated API docs and type definitions to reflect the new outcome contract and `pendingMarks` timing semantics.
* **Tests**
  * Expanded and adjusted coverage for the new typed outcome and mark-emission behavior.

Authors:
  - Bryan Bednarski (https://github.com/bbednarski9)
  - Will Killian (https://github.com/willkill07)

Approvers:
  - Will Killian (https://github.com/willkill07)

URL: #350

@lvojtku lvojtku 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.

Small comment

Comment thread docs/instrument-applications/advanced-guide.mdx Outdated
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@bbednarski9
bbednarski9 requested a review from lvojtku July 2, 2026 17:42
@coderabbitai coderabbitai Bot added the duplicate Issue/PR already exists label Jul 2, 2026
@willkill07 willkill07 removed the duplicate Issue/PR already exists label Jul 2, 2026

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

LGTM! thanks

@bbednarski9

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit dffe9bd into NVIDIA:main Jul 2, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Documentation documentation-related size:M PR is medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants