Skip to content

fix: retain native Responses user-turn history and instructions - #1643

Merged
mnajafian-nv merged 4 commits into
NVIDIA:release/0.11from
bbednarski9:bbednarski/fix-responses-history-user-items
Nov 4, 2026
Merged

mnajafian-nv merged 4 commits into
NVIDIA:release/0.11from
bbednarski9:bbednarski/fix-responses-history-user-items

Conversation

@bbednarski9

@bbednarski9 bbednarski9 commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Overview

After Codex uses a tool, its next model request contains the user’s prompt, the tool call, and the tool’s result. Relay can accidentally export only the result, losing the messages that explain why the tool ran.

This PR fixes the history filter so the exported conversation keeps that context.

For example, in the reproduced case:

Model request:   instructions → user prompt → tool call → tool result
Exported before:                                         tool result
Exported after:  instructions → user prompt → tool call → tool result

The model receives the complete request throughout. The bug affects the history Relay records in telemetry.

  • 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

Why it happens. When full-history export is disabled, Relay trims later model requests to the current user turn. OpenAI Responses messages can carry an item ID or extra provider fields. Relay keeps those messages in their original provider format to preserve those fields, but the history filter only recognized its regular User message type. It missed the user prompt in these requests and fell back to keeping the final item—often the tool result.

What changes. The filter reads the message’s role even when Relay preserves its original provider format. It keeps the latest user prompt and everything after it, together with system and developer instructions. Developer instructions also needed to be added to the retained instruction types. Item IDs and extra fields remain intact.

Validation. Reproduced on Codex CLI 0.162.1 with live-model and controlled-provider runs. The focused regression covers messages with and without IDs, prior conversation history, and tool calls/results. Rust, Python, and Node canonical suites passed on a combined checkout of the three related fixes; repository pre-commit hooks passed on this branch.

This restores missing conversation evidence. Custom-tool normalization is covered by #1644, and ownership hints by #1645. Establishing the link between a code-mode wrapper and its inner executions remains separate work.

Where should the reviewer start?

Start with the before/after regression, then the message-role helper and history filter.

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

  • Relates to: none

Summary by CodeRabbit

  • Bug Fixes
    • OpenAI Responses history projection now correctly identifies user turns from native message roles, including messages with item IDs or additional provider fields. System and developer instructions are retained, while the provider request remains unchanged.

Recognize explicit user and system roles on lossless native Responses messages when projecting telemetry to the current user turn. Preserve item IDs and provider fields without changing upstream requests.

Add a regression covering current-turn and multi-turn histories with and without provider item IDs.

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

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 7448918 and 6dee235.

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

No actionable comments were generated in the recent review.

Walkthrough

Turn projection now recognizes explicit roles in OpenAI Responses native messages of kind message. It uses those roles to identify the current user turn and retain system and developer instructions. Tests and documentation cover the projection behavior.

Changes

OpenAI Responses turn projection

Layer / File(s) Summary
Role-based turn projection
crates/core/src/api/llm.rs, crates/core/tests/unit/llm_api_tests.rs, docs/configure-plugins/observability/opentelemetry.mdx
A shared role lookup recognizes user, system, and developer roles in qualifying native messages. Projection detection and history retention use these roles. Tests cover history variants and preserved fields. Documentation describes the behavior and states that projection does not change the provider request.

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

Change: Bug fix

Merge Risk: Minimal · previous review 73a3d

Current-turn telemetry retains native user messages and instructions. No merge-blocking risk was identified.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title follows Conventional Commits format, uses the allowed lowercase type "fix," provides a concise imperative summary, is 63 characters long, and has no trailing period. It accurately describes …
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.
Description check Passed The description follows the required template. It includes an overview, checked confirmations, implementation details, validation results, reviewer starting points, and a Related Issues section using …
Full details: Title check

Explanation

The title follows Conventional Commits format, uses the allowed lowercase type "fix," provides a concise imperative summary, is 63 characters long, and has no trailing period. It accurately describes the main change.

Full details: Description check

Explanation

The description follows the required template. It includes an overview, checked confirmations, implementation details, validation results, reviewer starting points, and a Related Issues section using the required action keyword. The Related Issues entry does not identify a specific issue, but the description is otherwise complete and directly relevant.

Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 3 functions across 2 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 57184ba and 73a3d99.

Files selected for processing: 3
  • crates/core/src/api/llm.rs
  • crates/core/tests/unit/llm_api_tests.rs
  • docs/configure-plugins/observability/opentelemetry.mdx

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

Recent review details
Additional context used
Code guidelines: 5
.agents/skills/contribute-docs/SKILL.md — configured
.agents/skills/maintain-observability/SKILL.md — configured
AGENTS.md — configured
.agents/skills/add-binding-feature/SKILL.md — configured
.agents/skills/review-doc-style/SKILL.md — configured
Path-based instructions: 8
Review documentation for technical accuracy against the current API, command correctness, and consistency across language bindings.

CodeRabbit configuration file

Files:

  • docs/configure-plugins/observability/opentelemetry.mdx
Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.

CodeRabbit configuration file

Files:

  • crates/core/tests/unit/llm_api_tests.rs
Review the Rust runtime for async correctness, scope isolation, middleware ordering, and event lifecycle regressions.

CodeRabbit configuration file

Files:

  • crates/core/tests/unit/llm_api_tests.rs
  • crates/core/src/api/llm.rs
Source excerpt: In MDX files, top-of-file comments must use JSX comment delimiters: `{/*` to open and `*/}` to close.

CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

Files:

  • docs/configure-plugins/observability/opentelemetry.mdx
Source excerpt: Core event model and emitted fields `crates/core/src/observability/atif.rs` `crates/core/src/observability/otel.rs` `crates/core/src/observability/openinference.rs` FFI and binding-native wrappers where the config or lifecyc...

CodeRabbit inference engine (.agents/skills/maintain-observability/SKILL.md)

Files:

  • docs/configure-plugins/observability/opentelemetry.mdx
Source excerpt: Update public documentation and examples when public behavior, package names, supported bindings, or documented workflows change.

CodeRabbit inference engine (AGENTS.md)

Files:

  • docs/configure-plugins/observability/opentelemetry.mdx
Source excerpt: [ ] Core function with doc comment in `crates/core/src/api/`

CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

Files:

  • crates/core/src/api/llm.rs
Source excerpt: Verify MDX files use JSX delimiters for top-of-file SPDX comments.

CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

Files:

  • docs/configure-plugins/observability/opentelemetry.mdx
Additional comments: 3
crates/core/src/api/llm.rs: 1

396-413: LGTM!

Also applies to: 424-425, 455-456

crates/core/tests/unit/llm_api_tests.rs: 1

2850-2898: LGTM!

docs/configure-plugins/observability/opentelemetry.mdx: 1

1183-1188: LGTM!

@github-actions github-actions Bot added size:S PR is small Bug issue describes bug; PR fixes bug lang:rust PR changes/introduces Rust code labels Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

@willkill07 willkill07 added this to the 0.11 milestone Nov 4, 2026
@bbednarski9
bbednarski9 marked this pull request as ready for review November 4, 2026 20:47
@bbednarski9
bbednarski9 requested review from a team as code owners November 4, 2026 20:47
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>

bbednarski9 commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor Author

fresh before/after smoke test with a live model, using the same prompt on Codex CLI 0.162.1 / gpt-6.1-sol:

Run exactly one shell command printf relay-1643-smoke-ok using tools.exec_command from the exec tool, then reply with the command output. Do not use any other tools or add commentary before the call.

Each run completed one shell invocation, made two model requests, and answered relay-1643-smoke-ok. The comparison below is the second model request, after the tool completed. enable_full_payloads=false in both runs; the after build contains only this PR's fix, without #1644 or #1645.

Follow-up request Before: release baseline 57184ba79 After: this PR 7448918d3
Items actually sent upstream 9 9
Items retained in telemetry 1 8
Current user prompt Missing Retained
Model-requested tool call Missing Retained
Tool result Retained Retained
Developer instruction/tool-definition items 0 5

Telemetry IDs are anonymized below. Repeated labels refer to the same original identifier.

Before — the complete exported data.content.input contained only the result:

[
  {
    "call_id": "ID001",
    "id": "ID002",
    "output": [
      {
        "text": "Script completed\nWall time 0.2 seconds\nOutput:\n",
        "type": "input_text"
      },
      {
        "text": "relay-1643-smoke-ok",
        "type": "input_text"
      }
    ],
    "type": "custom_tool_call_output"
  }
]

After — the user prompt, tool call, and matching result are all present:

These are the actual non-developer entries from the same data.content.input field. The five retained developer items are omitted from this display; the three entries below preserve the captured content with identifiers replaced by consistent placeholders.

[
  {
    "content": [
      {
        "text": "Run exactly one shell command printf relay-1643-smoke-ok using tools.exec_command from the exec tool, then reply with the command output. Do not use any other tools or add commentary before the call.",
        "type": "input_text"
      }
    ],
    "id": "ID003",
    "role": "user",
    "type": "message"
  },
  {
    "call_id": "ID004",
    "id": "ID005",
    "input": "const r = await tools.exec_command({cmd:\"printf relay-1643-smoke-ok\"}); text(r.output);\n",
    "name": "exec",
    "status": "completed",
    "type": "custom_tool_call"
  },
  {
    "call_id": "ID004",
    "id": "ID006",
    "output": [
      {
        "text": "Script completed\nWall time 0.2 seconds\nOutput:\n",
        "type": "input_text"
      },
      {
        "text": "relay-1643-smoke-ok",
        "type": "input_text"
      }
    ],
    "type": "custom_tool_call_output"
  }
]

The after capture retains the prompt's message ID, the call's original JavaScript input, and the same call_id on the call and result. The earlier environment/user-context item is trimmed according to the existing current-turn policy. Both upstream requests still contained all nine input items.

@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

@mnajafian-nv
mnajafian-nv merged commit da5acab into NVIDIA:release/0.11 Nov 4, 2026
97 checks passed
mnajafian-nv pushed a commit that referenced this pull request Nov 4, 2026
#### Overview

Relay can already use a tool-call ID from a model response to help
identify which agent owns a tool execution. It collects that evidence
for function calls, but skips custom tool calls.

As a result, Codex can return the exact same ID in a custom tool's
execution hook and Relay still falls back without recognizing the match.
This PR includes custom calls in the existing ownership-hint matcher.

```text
Model requests custom apply_patch: ID001
Codex executes it; hook reports:  ID001

Before: fallback; custom provider call was skipped
After:  matching custom-call hint carries ID001 and its owner
```

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

**What changes.** Collect the custom call's invocation ID, name,
original input text, and response owner. Pass that evidence to the
existing matcher. The provider's item ID stays separate from the
invocation ID.

**When it can match.** A hook must share the invocation ID, or have an
unambiguous tool-name-and-argument match. Existing matching rules stay
unchanged. Tied matches and unrelated executions retain fallback
ownership.

**When it cannot match.** A code-mode wrapper can have a provider
`call_*` ID while its inner execution hook has an `exec-*` ID.
Collecting a wrapper hint does not establish that missing relationship.
Unmatched hints can change the diagnostic status to `ambiguous_fallback`
while leaving ownership unchanged.

**Validation.** Compared the release baseline with this patch alone
using the installed Codex CLI. Focused Rust regressions passed;
canonical Rust, Python, and Node suites passed on the combined checkout
of the three related fixes. Repository pre-commit hooks passed on this
branch. The follow-up smoke-test comment shows captured before/after
telemetry with masked identifiers.

The positive smoke case uses a scripted Responses provider and the real
Codex CLI: the CLI executes a direct custom `apply_patch` and emits
matching hook IDs. Live-model code-mode calls are checked separately
because they produce different inner IDs. The SessionManager regression
also covers exact-ID child ownership and ambiguous/unrelated hooks.

History retention (#1643) and standard message normalization (#1644) are
separate changes.

#### Where should the reviewer start?

Start with [the direct-custom and ambiguous-hook
regression](https://github.com/bbednarski9/NeMo-Relay/blob/60c4ed24c5852e015864c0fa6e1aeee5f18c49f8/crates/cli/tests/coverage/shared/session_tests.rs#L11940),
then [the custom-call hint
collector](https://github.com/bbednarski9/NeMo-Relay/blob/60c4ed24c5852e015864c0fa6e1aeee5f18c49f8/crates/cli/src/sessions/correlation.rs#L83).

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

- Relates to: none


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **New Features**
* Tool activity from OpenAI Responses custom tool calls can now be
associated with the corresponding tool hooks. Matching uses explicit
call IDs when available, or clear name-and-input matches; ambiguous
cases retain fallback attribution.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Bryan Bednarski <bbednarski@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.

The history projection now recognizes native Responses user messages and retains system and developer instructions while preserving the original provider items. I found no required corrections in the changed code.

willkill07 pushed a commit that referenced this pull request Nov 5, 2026
#### Overview

Codex's built-in `exec` tool accepts JavaScript generated by the model.
The Responses API represents this as `custom_tool_call`, with free-form
text in `input`. Here, **“custom” is the API type name**; it also covers
built-in agent tools, rather than implying a user-built tool or plugin.

Relay keeps the provider's raw JSON for these free-form calls but leaves
them out of its standard model-message fields. A consumer reading those
fields cannot see the model's tool request or match it to the result
sent back later.

This PR makes Responses free-form tool calls and results visible through
the same standard telemetry fields already used for function calls.

```text
Provider: free-form tool call ID001 → tool runs → result for ID001
Before:   raw JSON available; standard call/result message parts missing
After:    tool_call ID001                   → tool_call_response ID001
```

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

**Why it happens.** The Responses decoder and telemetry projections
recognize function calls but skip the `custom_tool_call` and
`custom_tool_call_output` items that Codex uses for code-mode scripts.

**What changes.** Recognize `custom_tool_call` and
`custom_tool_call_output` in normalized responses, GenAI messages,
OpenInference, and ATIF. Use the provider's `call_id` to connect a call
with its result. Keep the input as its original text—even when it looks
like JSON—and preserve the original provider items, IDs, and extra
fields.

**Validation.** Compared the release baseline with this patch alone
using the installed Codex CLI. Focused Rust regressions passed;
canonical Rust, Python, and Node suites passed on the combined checkout
of the three related fixes. Repository pre-commit hooks passed on this
branch. The follow-up smoke-test comment shows captured before/after
telemetry with masked identifiers.

The normalization smoke test uses `enable_full_payloads=true` so #1643's
separate history-filter bug cannot hide the input messages being
checked. This PR is still useful with full history enabled: retaining
raw data alone does not populate the standard tool-call fields.

This preserves the model-requested wrapper call. The relationship
between that wrapper and its individual inner executions remains
separate work; #1645 adds ownership hints when matching IDs are
available.

#### Where should the reviewer start?

Start with [the codec regression for free-form
calls](https://github.com/bbednarski9/NeMo-Relay/blob/db539ba33239fdd39575ec6607c6eca388918cfe/crates/core/tests/unit/codec/openai_responses_tests.rs#L1922),
then [the parser and telemetry
adapter](https://github.com/bbednarski9/NeMo-Relay/blob/db539ba33239fdd39575ec6607c6eca388918cfe/crates/core/src/codec/openai_responses.rs#L288).

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

- Relates to: none


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **New Features**
* OpenAI Responses custom tool calls and results are supported in
decoded messages, with call IDs, names, free-form input, and outputs
preserved.
* Observability data includes tool-call identifiers, arguments, and
correlated results more consistently across request and response
projections.
* **Bug Fixes**
* Custom tool calls use their wrapper call ID and input string in
projections, rather than unrelated item identifiers or fields.
* Invalid or incomplete custom tool-call entries are excluded from
portable projections.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

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

This branch was previously deployed

1 inactive deployment
fern — 6dee2358 Deployed Nov 4, 2026 by mnajafian-nv via Clean up docs preview #7033
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug issue describes bug; PR fixes bug lang:rust PR changes/introduces Rust code size:S PR is small

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants