Skip to content

feat(plugin)!: support pending marks from tool execution intercepts#350

Merged
rapids-bot[bot] merged 9 commits into
NVIDIA:mainfrom
bbednarski9:feat/tool-execution-pending-marks
Jul 2, 2026
Merged

feat(plugin)!: support pending marks from tool execution intercepts#350
rapids-bot[bot] merged 9 commits into
NVIDIA:mainfrom
bbednarski9:feat/tool-execution-pending-marks

Conversation

@bbednarski9

@bbednarski9 bbednarski9 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

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.

@github-actions github-actions Bot added size:XL PR is extra large lang:rust PR changes/introduces Rust code labels Jul 2, 2026
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

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

Adds a canonical tool execution intercept outcome carrying pending marks, then threads that typed shape through runtime execution, plugin bridges, and language bindings with matching tests and fixtures.

Changes

Tool Execution Intercept Outcome Rollout

Layer / File(s) Summary
Core outcome type and serialization
crates/types/src/api/tool.rs, crates/types/src/api/event.rs, crates/types/tests/serialization_tests.rs
Adds ToolExecutionInterceptOutcome, updates PendingMarkSpec documentation, and adds serialization coverage for the new outcome shape.
Runtime callback contract
crates/core/src/api/runtime/callbacks.rs, crates/core/src/api/runtime/state.rs
Updates runtime callback types and execution-chain wiring so tool execution intercepts resolve to the typed outcome and downstream pending marks are sequenced and merged.
Tool call lifecycle and marks
crates/core/src/api/tool.rs
Snapshots lifecycle subscribers during tool execution and emits pending marks as MarkEvents after the tool-end event.
Guardrails intercept return shape
crates/core/src/plugins/nemo_guardrails/python.rs, crates/core/src/plugins/nemo_guardrails/remote.rs
Updates the local and remote guardrails tool execution intercepts to return the converted tool result shape.
Native, worker, FFI, and plugin bridges
crates/core/src/plugin/dynamic/native.rs, crates/core/src/plugin/dynamic/worker.rs, crates/ffi/nemo_relay.h, crates/ffi/src/callable.rs, crates/plugin/src/lib.rs, crates/worker-proto/..., crates/worker/src/lib.rs
Updates the native, worker, FFI, and plugin SDK bridges to decode, validate, serialize, and return ToolExecutionInterceptOutcome instead of raw JSON.
Fixtures and examples
crates/core/tests/fixtures/*, examples/rust-native-plugin/src/lib.rs
Updates native and worker fixtures plus the Rust native example to construct the typed outcome and adds native fixture entry points for malformed and error outcome scenarios.
Core integration and unit tests
crates/core/tests/integration/*, crates/core/tests/unit/*, crates/adaptive/src/intercepts.rs, crates/adaptive/tests/*
Updates middleware, native and worker plugin, plugin registration, API surface, and adaptive warm-first tests to construct and assert the typed outcome and pending-mark ordering and timing.
Node bindings and tests
crates/node/plugin.d.ts, crates/node/src/*, crates/node/tests/*
Adds Node type definitions and wrappers for the typed outcome and updates Node tests to validate wrapped results and pending marks.
Python native extension bindings
crates/python/src/py_callable.rs, crates/python/src/py_types/*, crates/python/tests/coverage/*
Adds the Python-exposed outcome wrapper, updates the Python callable bridge, and revises coverage tests to use ToolOutcome and typed outcomes.
Python public API and plugin SDK
python/nemo_relay/*, python/plugin/src/nemo_relay_plugin/*, python/tests/*
Re-exports ToolExecutionInterceptOutcome across Python package and plugin SDK surfaces, updates type stubs and plugin validation, and revises Python integration tests for worker, scope-local, and tool execution intercepts.
Go bindings and tests
go/nemo_relay/*
Introduces ToolExecutionInterceptOutcome in the Go bindings, updates the trampoline marshaling path, and rewrites Go intercept tests to return the new outcome contract.

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

Sequence Diagram(s)

sequenceDiagram
  participant Intercept
  participant Runtime
  participant ToolCall
  participant Bridge
  participant Tests
  Intercept->>Runtime: return ToolExecutionInterceptOutcome
  Runtime->>ToolCall: emit tool-end and pending marks
  Bridge->>Intercept: validate/serialize typed outcome
  Tests->>Bridge: assert wrapped result + pending_marks
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed It matches Conventional Commits and accurately summarizes the breaking change.
Description check ✅ Passed It covers the change summary, motivation, behavior, boundary contracts, breaking changes, and validation, though the template’s checkbox and reviewer-start sections are omitted.
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.

@bbednarski9 bbednarski9 changed the title [codex] feat(plugin)!: support pending marks from tool intercepts feat(plugin)!: support pending marks from tool intercepts Jul 2, 2026
@github-actions github-actions Bot added Feature a new feature breaking PR introduces a breaking change labels Jul 2, 2026
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

@willkill07 willkill07 added this to the 0.5 milestone Jul 2, 2026
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@bbednarski9 bbednarski9 force-pushed the feat/tool-execution-pending-marks branch from 127a806 to b9dc629 Compare July 2, 2026 02:10
@github-actions github-actions Bot added lang:go PR changes/introduces Go code lang:js PR changes/introduces Javascript/Typescript code lang:python PR changes/introduces Python code labels Jul 2, 2026
@bbednarski9 bbednarski9 changed the title feat(plugin)!: support pending marks from tool intercepts feat(plugin)!: support pending marks from tool execution intercepts Jul 2, 2026
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

License Diff

Compared against origin/main.

Lockfile license changes

Lockfile License Changes

Rust

Added

  • None

Removed

  • None

Updated/Changed

  • None

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 (350 packages)
[license-diff] current: generating Node inventory
[license-diff] current: Node inventory complete (363 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 (350 packages)
[license-diff] base: generating Node inventory
[license-diff] base: Node inventory complete (363 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

@bbednarski9 bbednarski9 marked this pull request as ready for review July 2, 2026 02:36
@bbednarski9 bbednarski9 requested a review from a team as a code owner July 2, 2026 02:36

@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: 5

Caution

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

⚠️ Outside diff range comments (2)
go/nemo_relay/tools_test.go (1)

442-495: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

No Go test exercises PendingMarks propagation.

Every updated callback here (and across callbacks_test.go, deregister_test.go, error_test.go, scope_local_test.go, intercepts_test.go, adaptive_plugin_test.go) only sets Result and leaves PendingMarks nil/empty. None of the Go tests construct a non-empty PendingMarks slice or assert that marks emitted from a tool execution intercept reach the caller/lifecycle subscriber — despite pending-mark support being the headline feature added by this PR. This full pipeline test (TestToolFullPipelineInterceptsAndExecute) is a natural place to extend coverage with a PendingMarkSpec and an assertion on its emission/ordering.

As per path instructions, "Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant," and any Go API change "should include focused Go tests."

🤖 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 `@go/nemo_relay/tools_test.go` around lines 442 - 495, Extend
TestToolFullPipelineInterceptsAndExecute to cover PendingMarks propagation
through the full ToolCallExecute pipeline. In the execution intercept callback
and/or the tool callable path, return a non-empty PendingMarks slice using the
existing ToolExecutionInterceptOutcome and PendingMarkSpec types, then assert
the caller receives those marks in the expected order alongside the Result. Use
the existing symbols ToolCallExecute, RegisterToolExecutionIntercept, and
PendingMarkSpec to locate the flow, and mirror the same coverage pattern in the
related callback/intercept tests if needed.

Source: Path instructions

crates/worker/tests/worker_sdk_tests.rs (1)

508-519: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Strengthen pending-mark round-trip coverage at the worker RPC boundary.

The pending mark built here only sets name, and the test only asserts pending_marks[0].name. Per PR objectives, marks should retain category, category profile, data, and metadata across the boundary — this is the only test exercising PendingMarkSpec through the new JsonEnvelope/gRPC serialization path, so a regression dropping those fields on encode/decode wouldn't be caught here.

As per path instructions, {crates/**/tests/**,...}: "Tests should cover the behavior promised by the changed API surface... Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests."

♻️ Proposed test strengthening
         ctx.register_tool_execution_intercept("tool-exec", 1, move |_, value, next: ToolNext| {
             let runtime = tool_runtime.clone();
             async move {
                 ...
                 Ok(ToolExecutionInterceptOutcome::new(set_json_field(
                     next_value,
                     "phase",
                     "tool_exec",
                 ))
                 .with_pending_mark(
                     PendingMarkSpec::builder()
                         .name("worker.tool.execution")
+                        .category("custom")
+                        .data(json!({"count": 1}))
+                        .metadata(json!({"source": "worker"}))
                         .build(),
                 ))
             }
         });
     let tool_outcome = invoke_tool_execution(...).await;
     assert_eq!(tool_outcome.pending_marks.len(), 1);
-    assert_eq!(tool_outcome.pending_marks[0].name, "worker.tool.execution");
+    let mark = &tool_outcome.pending_marks[0];
+    assert_eq!(mark.name, "worker.tool.execution");
+    assert_eq!(mark.category.as_deref(), Some("custom"));
+    assert_eq!(mark.data, Some(json!({"count": 1})));
+    assert_eq!(mark.metadata, Some(json!({"source": "worker"})));

Also applies to: 1546-1555

🤖 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/tests/worker_sdk_tests.rs` around lines 508 - 519, The
PendingMarkSpec round-trip test in worker_sdk_tests should verify all fields
survive the worker RPC boundary, not just name. Update the tool
invocation/assertions around invoke_tool_execution, tool_outcome, and tool_exec
to populate and compare category, category profile, data, and metadata alongside
pending_marks[0].name. Strengthen the test so it exercises the JsonEnvelope/gRPC
serialization path and fails if any PendingMarkSpec field is dropped or altered
during encode/decode.

Source: Path instructions

🤖 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/ffi/src/callable.rs`:
- Around line 390-392: The ownership/lifetime handling in
callable::json_result_from_ptr usage is unsafe because an invalid non-null
callback string can return early before result_ptr is freed. Update the tool
execution intercept callback flow in callable.rs so result_ptr is always
released before any parse error is propagated, ideally by ensuring the free
happens on the error path or via a scope/guard around the json_result_from_ptr
call. Keep the fix localized to the callback handling around
json_result_from_ptr and nemo_relay_string_free_internal.

In `@crates/ffi/tests/unit/callable_tests.rs`:
- Around line 292-294: The current test only verifies the empty pending-marks
path, so add another callback case around wrap_tool_exec_intercept_fn that
returns a populated pending_marks entry and assert the wrapped result preserves
its name, category, profile, data, and metadata. Update the callable_tests
coverage to exercise the FFI wrapper’s promised API behavior by checking both
the intercepted payload and a non-empty pending_marks array.

In `@crates/node/plugin.d.ts`:
- Around line 48-61: The inline pendingMarks shape in
registerLlmRequestIntercept duplicates the new PendingMarkSpec interface, so
update the registerLlmRequestIntercept callback/options types to reference
PendingMarkSpec[] instead of redefining the object shape. Keep the
PendingMarkSpec interface as the single source of truth and reuse it wherever
the pending mark array is typed, including the ToolExecutionInterceptOutcome
contract if applicable.

In `@crates/node/tests/tools_tests.mjs`:
- Around line 630-670: The Node tool execution test in toolCallExecute should
also verify event ordering, not just parentage. In the existing
replaced_tool/intercept flow, capture the tool end event alongside the pending
mark and assert the pending mark is observed after the end event, matching the
contract already covered by
test_tool_execution_outcome_marks_follow_end_with_tool_parentage. Use the
existing event collection in tools_tests.mjs and the current start/mark
assertions to add an end-before-mark ordering check.

In `@crates/python/src/py_callable.rs`:
- Around line 440-447: The same PyToolExecutionInterceptOutcome extraction and
FlowError::Internal mapping is duplicated in both the sync and async branches of
py_callable.rs. Extract that repeated logic into a small helper such as
extract_tool_outcome and have both the direct result path and the awaited
coroutine path call it, so the error message and extraction behavior stay
consistent in the tool interception flow.

---

Outside diff comments:
In `@crates/worker/tests/worker_sdk_tests.rs`:
- Around line 508-519: The PendingMarkSpec round-trip test in worker_sdk_tests
should verify all fields survive the worker RPC boundary, not just name. Update
the tool invocation/assertions around invoke_tool_execution, tool_outcome, and
tool_exec to populate and compare category, category profile, data, and metadata
alongside pending_marks[0].name. Strengthen the test so it exercises the
JsonEnvelope/gRPC serialization path and fails if any PendingMarkSpec field is
dropped or altered during encode/decode.

In `@go/nemo_relay/tools_test.go`:
- Around line 442-495: Extend TestToolFullPipelineInterceptsAndExecute to cover
PendingMarks propagation through the full ToolCallExecute pipeline. In the
execution intercept callback and/or the tool callable path, return a non-empty
PendingMarks slice using the existing ToolExecutionInterceptOutcome and
PendingMarkSpec types, then assert the caller receives those marks in the
expected order alongside the Result. Use the existing symbols ToolCallExecute,
RegisterToolExecutionIntercept, and PendingMarkSpec to locate the flow, and
mirror the same coverage pattern in the related callback/intercept tests if
needed.
🪄 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: d012fd55-3115-46cf-9ead-c919f10ae59f

📥 Commits

Reviewing files that changed from the base of the PR and between c28ea85 and 2238c29.

📒 Files selected for processing (62)
  • crates/adaptive/src/intercepts.rs
  • crates/adaptive/tests/unit/intercepts_tests.rs
  • crates/adaptive/tests/unit/runtime_features_tests.rs
  • crates/core/src/api/runtime/callbacks.rs
  • crates/core/src/api/runtime/state.rs
  • crates/core/src/api/tool.rs
  • crates/core/src/plugin/dynamic/native.rs
  • crates/core/src/plugin/dynamic/worker.rs
  • crates/core/src/plugins/nemo_guardrails/python.rs
  • crates/core/src/plugins/nemo_guardrails/remote.rs
  • crates/core/tests/fixtures/native_plugin/src/lib.rs
  • crates/core/tests/fixtures/worker_plugin/src/main.rs
  • crates/core/tests/integration/api_surface_tests.rs
  • crates/core/tests/integration/middleware_tests.rs
  • crates/core/tests/integration/native_plugin_tests.rs
  • crates/core/tests/integration/worker_plugin_tests.rs
  • crates/core/tests/unit/plugin_tests.rs
  • crates/ffi/nemo_relay.h
  • crates/ffi/src/callable.rs
  • crates/ffi/tests/integration/api_tests.rs
  • crates/ffi/tests/unit/api/registry_tests.rs
  • crates/ffi/tests/unit/api_tests.rs
  • crates/ffi/tests/unit/callable_tests.rs
  • crates/node/plugin.d.ts
  • crates/node/src/api/mod.rs
  • crates/node/src/callable.rs
  • crates/node/tests/scope_local_tests.mjs
  • crates/node/tests/tools_tests.mjs
  • crates/plugin/src/lib.rs
  • crates/plugin/tests/typed_callbacks.rs
  • crates/python/src/py_callable.rs
  • crates/python/src/py_types/core.rs
  • crates/python/src/py_types/mod.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/python/tests/coverage/py_api_coverage_tests.rs
  • crates/python/tests/coverage/py_callable_coverage_tests.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/types/src/api/event.rs
  • crates/types/src/api/tool.rs
  • crates/types/tests/serialization_tests.rs
  • crates/worker-proto/proto/nemo/relay/worker/v1/plugin_worker.proto
  • crates/worker/src/lib.rs
  • crates/worker/tests/worker_sdk_tests.rs
  • examples/rust-native-plugin/src/lib.rs
  • go/nemo_relay/adaptive_plugin_test.go
  • go/nemo_relay/callbacks.go
  • go/nemo_relay/callbacks_test.go
  • go/nemo_relay/deregister_test.go
  • go/nemo_relay/error_test.go
  • go/nemo_relay/intercepts/intercepts_test.go
  • go/nemo_relay/scope_local_test.go
  • go/nemo_relay/tools_test.go
  • python/nemo_relay/__init__.py
  • python/nemo_relay/__init__.pyi
  • python/nemo_relay/_native.pyi
  • python/nemo_relay/intercepts.py
  • python/nemo_relay/scope_local.py
  • python/plugin/src/nemo_relay_plugin/__init__.py
  • python/plugin/src/nemo_relay_plugin/_api.py
  • python/tests/plugin/test_worker_sdk.py
  • python/tests/test_scope_local.py
  • python/tests/test_tools.py

Comment thread crates/ffi/src/callable.rs
Comment thread crates/ffi/tests/unit/callable_tests.rs Outdated
Comment thread crates/node/plugin.d.ts
Comment thread crates/node/tests/tools_tests.mjs
Comment thread crates/python/src/py_callable.rs
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>

@willkill07 willkill07 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

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

Copy link
Copy Markdown
Member

/merge

@rapids-bot rapids-bot Bot merged commit c448904 into NVIDIA:main Jul 2, 2026
64 of 66 checks passed
rapids-bot Bot pushed a commit that referenced this pull request Jul 2, 2026
#### Overview

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

- [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.

#### 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)

- Relates to #327
- Relates to #350

#### 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.

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

Approvers:
  - https://github.com/lvojtku
  - Will Killian (https://github.com/willkill07)
  - Maryam Najafian (https://github.com/mnajafian-nv)

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

Labels

breaking PR introduces a breaking change 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 size:XL PR is extra large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants