You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix: retain native Responses user-turn history and instructions - #1643
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.
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>
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.
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.
Current-turn telemetry retains native user messages and instructions. No merge-blocking risk was identified.
Pre-merge checks: 4 · 1
Failed checks: 1 warning
Check name
Status
Explanation
Resolution
Docstring Coverage
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 checks: 4 passed
Check name
Status
Explanation
Title check
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
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Description check
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.
Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.
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...
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:
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.
#### 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>
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.
#### 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Bugissue describes bug; PR fixes buglang:rustPR changes/introduces Rust codesize:SPR is small
4 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
The model receives the complete request throughout. The bug affects the history Relay records in telemetry.
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
Usermessage 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)
Summary by CodeRabbit