Skip to content

fix(responses): preserve author, recipient, encrypted_content in multi-agent collab_tool_call items - #4609

Merged
akshaydeo merged 1 commit into
maximhq:devfrom
Shaik-Sirajuddin:fix/collab-tool-call-dropped-fields
Jun 22, 2026
Merged

fix(responses): preserve author, recipient, encrypted_content in multi-agent collab_tool_call items#4609
akshaydeo merged 1 commit into
maximhq:devfrom
Shaik-Sirajuddin:fix/collab-tool-call-dropped-fields

Conversation

@Shaik-Sirajuddin

Copy link
Copy Markdown
Contributor

Fixes #4608

Problem

ResponsesMessage had no Author or Recipient struct fields, and ResponsesMessageContentBlock had no EncryptedContent field. Go's JSON decoder silently drops unknown fields on decode, so these values were stripped before bifrost re-serialized the request to the upstream provider.

This breaks Codex 0.141.0+ multi_agent_v2 subagent spawning entirely. OpenAI requires all three fields on collab_tool_call input items and on reasoning content blocks during history replay.

Changes

core/schemas/responses.go

// ResponsesMessage — two new fields:
Author    json.RawMessage `json:"author,omitempty"`
Recipient json.RawMessage `json:"recipient,omitempty"`

// ResponsesMessageContentBlock — one new field:
EncryptedContent *string `json:"encrypted_content,omitempty"`

json.RawMessage is used for author/recipient so bifrost passes the objects through verbatim without assuming OpenAI's multi-agent schema shape, which may evolve.

How to verify

# Requires Codex 0.141.0+ and bifrost proxying an OpenAI Responses API endpoint
codex exec \
  -c model_provider=bifrost \
  -c model=gpt-5.5 \
  --enable multi_agent_v2 \
  --dangerously-bypass-approvals-and-sandbox \
  "write hello to hello.txt then spawn a subagent to verify it exists"

Before fix: 400 Missing required parameter: 'input[N].author' on the second request.
After fix: Session completes — subagent spawns, verifies file, reports back. All /v1/responses return 200.

Test plan

  • Existing unit/integration tests pass
  • Manual end-to-end: Codex multi_agent_v2 subagent spawn through bifrost returns 200 on all turns
  • Verify author, recipient, encrypted_content are present in the request bifrost forwards upstream (can check with send_back_raw_request: true on a deliberate error)

…i-agent items

ResponsesMessage had no Author or Recipient fields, and
ResponsesMessageContentBlock had no EncryptedContent field. Go's JSON
decoder silently drops unknown fields during decode, so these values
were stripped out before bifrost re-serialized the request upstream.

OpenAI requires all three on collab_tool_call items used by Codex
multi_agent_v2 (>=0.141.0):
- author: identifies the sending agent on collab_tool_call input items
- recipient: identifies the target agent on collab_tool_call input items
- encrypted_content: opaque reasoning token on content blocks that must
  be echoed verbatim on history replay

author and recipient use json.RawMessage to survive future schema
changes without coupling bifrost to OpenAI's object shape.
encrypted_content is a *string (opaque base64 blob).

Verified end-to-end: Codex multi_agent_v2 subagent spawn completes
successfully through bifrost with all /v1/responses returning 200.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Jun 22, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 43fcdc29-a4a8-4ce7-8b74-97d098361f33

📥 Commits

Reviewing files that changed from the base of the PR and between 1cdd311 and 47030bb.

📒 Files selected for processing (1)
  • core/schemas/responses.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Enhanced multi-agent collaboration by introducing author and recipient tracking for collaborative tool calls, improving visibility across agent interactions.
    • Added encrypted content support for reasoning blocks, strengthening security and ensuring accurate playback during conversation history review.

Walkthrough

Three fields are added to core/schemas/responses.go: Author and Recipient (as json.RawMessage) on ResponsesMessage for multi-agent collab_tool_call items, and EncryptedContent (as *string) on ResponsesMessageContentBlock for reasoning history replay. The encoding/json import is added to support the raw message types.

Changes

Responses Schema Field Additions

Layer / File(s) Summary
Add Author, Recipient, EncryptedContent to response schemas
core/schemas/responses.go
encoding/json import added; ResponsesMessage extended with Author and Recipient as json.RawMessage (preserving multi-agent collab tool call objects verbatim); ResponsesMessageContentBlock extended with EncryptedContent *string for reasoning content blocks during history replay.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Possibly related PRs

  • maximhq/bifrost#3528: Also modifies ResponsesMessage in core/schemas/responses.go by adding new JSON-serialized struct fields, directly overlapping with this PR's schema touchpoints.
  • maximhq/bifrost#3584: Uses ResponsesMessage and content-block schemas in mux conversion logic, including handling of reasoning/encrypted content fields added here.

Suggested reviewers

  • danpiths
  • akshaydeo

Poem

🐇 Three fields were missing, the schema incomplete,
author and recipient lost on the street.
encrypted_content vanished in the round-trip air,
A 400 from OpenAI said "fields aren't there!"
I hopped in, added them — RawMessage and string,
Now Codex subagents can do their thing! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding three fields to response schemas to preserve multi-agent collaboration data during JSON serialization.
Description check ✅ Passed The description provides clear problem statement, specific code changes, and detailed verification steps, though test plan items are marked as incomplete rather than confirmed.
Linked Issues check ✅ Passed The code changes directly address all requirements from issue #4608: adding Author and Recipient fields to ResponsesMessage and EncryptedContent to ResponsesMessageContentBlock using appropriate types.
Out of Scope Changes check ✅ Passed All changes are scoped to the three fields required by issue #4608 with no unrelated modifications to other functionality or files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai
coderabbitai Bot requested review from akshaydeo and danpiths June 22, 2026 11:03
@akshaydeo
akshaydeo merged commit 9350478 into maximhq:dev Jun 22, 2026
5 of 6 checks passed
@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 3/5

The schema change is correct and addresses the described failure, but the streaming accumulator's deep-copy helpers are not updated, leaving the new fields absent from the accumulated response consumed by post-hook plugins.

The fix correctly targets the JSON decode/re-encode path for request input items. However, deepCopyResponsesMessage and deepCopyResponsesMessageContentBlock in framework/streaming/responses.go — which are actively called for every streamed output item — do not copy Author, Recipient, or EncryptedContent. Any plugin that inspects or re-serializes the accumulated streaming response will see these fields silently absent, and as the multi-agent protocol evolves to return collab_tool_call output items, the same stripping problem reappears on the response side.

framework/streaming/responses.go — both deep-copy helpers need to mirror the new fields added to the schema structs.

Important Files Changed

Filename Overview
core/schemas/responses.go Adds Author/Recipient (json.RawMessage) to ResponsesMessage and EncryptedContent (*string) to ResponsesMessageContentBlock to preserve multi-agent fields through JSON round-trips; schema change is correct but streaming deep-copy helpers are not updated.
framework/streaming/responses.go deepCopyResponsesMessage does not copy the new Author/Recipient fields, and deepCopyResponsesMessageContentBlock does not copy EncryptedContent; both are actively called in the streaming accumulator, so these fields are silently dropped in the accumulated response passed to post-hook plugins.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Codex
    participant Bifrost
    participant OpenAI

    Codex->>Bifrost: POST /v1/responses (input includes collab_tool_call with author/recipient)
    Note over Bifrost: Unmarshal into ResponsesMessage<br/>author+recipient decoded as json.RawMessage ✅
    Bifrost->>OpenAI: Forward request (author+recipient preserved) ✅
    OpenAI-->>Bifrost: Stream response (output_item.added)
    Note over Bifrost: deepCopyResponsesMessage called<br/>Author/Recipient NOT copied ❌
    Note over Bifrost: Accumulated response passed to plugins<br/>author+recipient missing in plugin view
    Bifrost-->>Codex: Stream proxied (original bytes intact ✅)
    Codex->>Bifrost: Next turn: POST /v1/responses (history with prior items)
    Note over Bifrost: Schema fix ensures fields survive decode/re-encode ✅
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Codex
    participant Bifrost
    participant OpenAI

    Codex->>Bifrost: POST /v1/responses (input includes collab_tool_call with author/recipient)
    Note over Bifrost: Unmarshal into ResponsesMessage<br/>author+recipient decoded as json.RawMessage ✅
    Bifrost->>OpenAI: Forward request (author+recipient preserved) ✅
    OpenAI-->>Bifrost: Stream response (output_item.added)
    Note over Bifrost: deepCopyResponsesMessage called<br/>Author/Recipient NOT copied ❌
    Note over Bifrost: Accumulated response passed to plugins<br/>author+recipient missing in plugin view
    Bifrost-->>Codex: Stream proxied (original bytes intact ✅)
    Codex->>Bifrost: Next turn: POST /v1/responses (history with prior items)
    Note over Bifrost: Schema fix ensures fields survive decode/re-encode ✅
Loading

Reviews (1): Last reviewed commit: "fix(responses): preserve author, recipie..." | Re-trigger Greptile

Shaik-Sirajuddin added a commit to Shaik-Sirajuddin/bifrost that referenced this pull request Jun 22, 2026
…sage copies

PR maximhq#4609 added Author/Recipient on ResponsesMessage and EncryptedContent on
ResponsesMessageContentBlock to preserve Codex multi_agent_v2 collab_tool_call
fields through bifrost's JSON decode/re-encode path. The deep-copy helpers were
not updated to mirror them, so any accumulator that deep-copies a message (every
streamed output item, and request-side copies) silently drops these fields. As
the multi-agent protocol evolves to return collab_tool_call output items, the
same stripping reappears on the response side.

Mirror the new fields in both deep-copy implementations:
- framework/streaming/responses.go: deepCopyResponsesMessage +
  deepCopyResponsesMessageContentBlock (response/streaming accumulator path)
- core/schemas/utils.go: DeepCopyResponsesMessage +
  deepCopyResponsesMessageContentBlock (request-side copy)

Author/Recipient (json.RawMessage = []byte) are copied via append to a fresh
slice; EncryptedContent and the sibling reasoning field Signature (*string) are
value-copied. Also add the missing Phase (*string) copy in the core helper for
parity with the streaming helper.
Shaik-Sirajuddin added a commit to Shaik-Sirajuddin/bifrost that referenced this pull request Jun 22, 2026
…sage copies

PR maximhq#4609 added Author/Recipient on ResponsesMessage and EncryptedContent on
ResponsesMessageContentBlock to preserve Codex multi_agent_v2 collab_tool_call
fields through bifrost's JSON decode/re-encode path. The deep-copy helpers were
not updated to mirror them, so any accumulator that deep-copies a message (every
streamed output item, and request-side copies) silently drops these fields. As
the multi-agent protocol evolves to return collab_tool_call output items, the
same stripping reappears on the response side.

Mirror the new fields in both deep-copy implementations:
- framework/streaming/responses.go: deepCopyResponsesMessage +
  deepCopyResponsesMessageContentBlock (response/streaming accumulator path)
- core/schemas/utils.go: DeepCopyResponsesMessage +
  deepCopyResponsesMessageContentBlock (request-side copy)

Author/Recipient (json.RawMessage = []byte) are copied via append to a fresh
slice; EncryptedContent and the sibling reasoning field Signature (*string) are
value-copied. Also add the missing Phase (*string) copy in the core helper for
parity with the streaming helper.
Shaik-Sirajuddin added a commit to Shaik-Sirajuddin/bifrost that referenced this pull request Jun 22, 2026
…sage copies

PR maximhq#4609 added Author/Recipient on ResponsesMessage and EncryptedContent on
ResponsesMessageContentBlock to preserve Codex multi_agent_v2 collab_tool_call
fields through bifrost's JSON decode/re-encode path. The deep-copy helpers were
not updated to mirror them, so any accumulator that deep-copies a message (every
streamed output item, and request-side copies) silently drops these fields. As
the multi-agent protocol evolves to return collab_tool_call output items, the
same stripping reappears on the response side.

Mirror the new fields in both deep-copy implementations:
- framework/streaming/responses.go: deepCopyResponsesMessage +
  deepCopyResponsesMessageContentBlock (response/streaming accumulator path)
- core/schemas/utils.go: DeepCopyResponsesMessage +
  deepCopyResponsesMessageContentBlock (request-side copy)

Author/Recipient (json.RawMessage = []byte) are copied via append to a fresh
slice; EncryptedContent and the sibling reasoning field Signature (*string) are
value-copied. Also add the missing Phase (*string) copy in the core helper for
parity with the streaming helper.
akshaydeo added a commit that referenced this pull request Jun 23, 2026
…sage copies (#4612)

* fix(responses): deep-copy author, recipient, encrypted_content in message copies

PR #4609 added Author/Recipient on ResponsesMessage and EncryptedContent on
ResponsesMessageContentBlock to preserve Codex multi_agent_v2 collab_tool_call
fields through bifrost's JSON decode/re-encode path. The deep-copy helpers were
not updated to mirror them, so any accumulator that deep-copies a message (every
streamed output item, and request-side copies) silently drops these fields. As
the multi-agent protocol evolves to return collab_tool_call output items, the
same stripping reappears on the response side.

Mirror the new fields in both deep-copy implementations:
- framework/streaming/responses.go: deepCopyResponsesMessage +
  deepCopyResponsesMessageContentBlock (response/streaming accumulator path)
- core/schemas/utils.go: DeepCopyResponsesMessage +
  deepCopyResponsesMessageContentBlock (request-side copy)

Author/Recipient (json.RawMessage = []byte) are copied via append to a fresh
slice; EncryptedContent and the sibling reasoning field Signature (*string) are
value-copied. Also add the missing Phase (*string) copy in the core helper for
parity with the streaming helper.

* refactor(responses): use Ptr helper for pointer field copies

Address review feedback on #4612: collapse the verbose local-variable
pointer copies (Phase, Signature, EncryptedContent) to the Ptr helper.

* refactor(responses): use new(string) for pointer field copies

Replace the Ptr helper with the new(string) + value-assign form for the
Phase, Signature, and EncryptedContent pointer copies in both deep-copy
helpers, per review feedback.

---------

Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
akshaydeo pushed a commit that referenced this pull request Jun 24, 2026
…i-agent items (#4609)

ResponsesMessage had no Author or Recipient fields, and
ResponsesMessageContentBlock had no EncryptedContent field. Go's JSON
decoder silently drops unknown fields during decode, so these values
were stripped out before bifrost re-serialized the request upstream.

OpenAI requires all three on collab_tool_call items used by Codex
multi_agent_v2 (>=0.141.0):
- author: identifies the sending agent on collab_tool_call input items
- recipient: identifies the target agent on collab_tool_call input items
- encrypted_content: opaque reasoning token on content blocks that must
  be echoed verbatim on history replay

author and recipient use json.RawMessage to survive future schema
changes without coupling bifrost to OpenAI's object shape.
encrypted_content is a *string (opaque base64 blob).

Verified end-to-end: Codex multi_agent_v2 subagent spawn completes
successfully through bifrost with all /v1/responses returning 200.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
akshaydeo added a commit that referenced this pull request Jun 24, 2026
…sage copies (#4612)

* fix(responses): deep-copy author, recipient, encrypted_content in message copies

PR #4609 added Author/Recipient on ResponsesMessage and EncryptedContent on
ResponsesMessageContentBlock to preserve Codex multi_agent_v2 collab_tool_call
fields through bifrost's JSON decode/re-encode path. The deep-copy helpers were
not updated to mirror them, so any accumulator that deep-copies a message (every
streamed output item, and request-side copies) silently drops these fields. As
the multi-agent protocol evolves to return collab_tool_call output items, the
same stripping reappears on the response side.

Mirror the new fields in both deep-copy implementations:
- framework/streaming/responses.go: deepCopyResponsesMessage +
  deepCopyResponsesMessageContentBlock (response/streaming accumulator path)
- core/schemas/utils.go: DeepCopyResponsesMessage +
  deepCopyResponsesMessageContentBlock (request-side copy)

Author/Recipient (json.RawMessage = []byte) are copied via append to a fresh
slice; EncryptedContent and the sibling reasoning field Signature (*string) are
value-copied. Also add the missing Phase (*string) copy in the core helper for
parity with the streaming helper.

* refactor(responses): use Ptr helper for pointer field copies

Address review feedback on #4612: collapse the verbose local-variable
pointer copies (Phase, Signature, EncryptedContent) to the Ptr helper.

* refactor(responses): use new(string) for pointer field copies

Replace the Ptr helper with the new(string) + value-assign form for the
Phase, Signature, and EncryptedContent pointer copies in both deep-copy
helpers, per review feedback.

---------

Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ResponsesMessage drops author, recipient, encrypted_content fields — breaks Codex multi_agent_v2 subagent spawning

3 participants