From 854ae7eec07abe883b56cf09f29ad60e20311184 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 16 Jun 2026 19:39:45 +0200 Subject: [PATCH 1/6] Expose inter-agent communications as app-server items --- codex-rs/analytics/src/reducer.rs | 2 + .../schema/json/ClientRequest.json | 6 ++ .../schema/json/ServerNotification.json | 53 +++++++++++++++++ .../codex_app_server_protocol.schemas.json | 59 +++++++++++++++++++ .../codex_app_server_protocol.v2.schemas.json | 59 +++++++++++++++++++ .../json/v2/ItemCompletedNotification.json | 53 +++++++++++++++++ .../json/v2/ItemStartedNotification.json | 53 +++++++++++++++++ .../RawResponseItemCompletedNotification.json | 6 ++ .../schema/json/v2/ReviewStartResponse.json | 53 +++++++++++++++++ .../schema/json/v2/ThreadForkResponse.json | 53 +++++++++++++++++ .../schema/json/v2/ThreadListResponse.json | 53 +++++++++++++++++ .../json/v2/ThreadMetadataUpdateResponse.json | 53 +++++++++++++++++ .../schema/json/v2/ThreadReadResponse.json | 53 +++++++++++++++++ .../schema/json/v2/ThreadResumeParams.json | 6 ++ .../schema/json/v2/ThreadResumeResponse.json | 53 +++++++++++++++++ .../json/v2/ThreadRollbackResponse.json | 53 +++++++++++++++++ .../schema/json/v2/ThreadStartResponse.json | 53 +++++++++++++++++ .../json/v2/ThreadStartedNotification.json | 53 +++++++++++++++++ .../json/v2/ThreadUnarchiveResponse.json | 53 +++++++++++++++++ .../json/v2/TurnCompletedNotification.json | 53 +++++++++++++++++ .../schema/json/v2/TurnStartResponse.json | 53 +++++++++++++++++ .../json/v2/TurnStartedNotification.json | 53 +++++++++++++++++ .../schema/typescript/ResponseItemMetadata.ts | 2 +- .../schema/typescript/v2/ThreadItem.ts | 2 +- .../src/protocol/thread_history.rs | 7 +++ .../src/protocol/v2/item.rs | 29 +++++++++ .../src/protocol/v2/tests.rs | 31 ++++++++++ codex-rs/core/src/compact_tests.rs | 1 + .../core/src/context_manager/history_tests.rs | 1 + codex-rs/core/src/session/mod.rs | 26 ++++++++ codex-rs/core/src/session/tests.rs | 1 + .../handlers/multi_agents_v2/message_tool.rs | 7 ++- codex-rs/protocol/src/items.rs | 21 +++++++ codex-rs/protocol/src/models.rs | 4 ++ 34 files changed, 1115 insertions(+), 3 deletions(-) diff --git a/codex-rs/analytics/src/reducer.rs b/codex-rs/analytics/src/reducer.rs index 89d6e57dc5c4..1123af7d67f6 100644 --- a/codex-rs/analytics/src/reducer.rs +++ b/codex-rs/analytics/src/reducer.rs @@ -381,6 +381,7 @@ impl TurnToolCounts { ThreadItem::UserMessage { .. } | ThreadItem::HookPrompt { .. } | ThreadItem::AgentMessage { .. } + | ThreadItem::InterAgentCommunication { .. } | ThreadItem::Plan { .. } | ThreadItem::Reasoning { .. } | ThreadItem::ImageView { .. } @@ -1617,6 +1618,7 @@ fn tracked_tool_item_id(item: &ThreadItem) -> Option<&str> { ThreadItem::UserMessage { .. } | ThreadItem::HookPrompt { .. } | ThreadItem::AgentMessage { .. } + | ThreadItem::InterAgentCommunication { .. } | ThreadItem::Plan { .. } | ThreadItem::Reasoning { .. } | ThreadItem::SubAgentActivity { .. } diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 3ed2791c8551..eefa94ac4883 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -2937,6 +2937,12 @@ }, "ResponseItemMetadata": { "properties": { + "source_call_id": { + "type": [ + "string", + "null" + ] + }, "turn_id": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 1cf3d96c8172..59d753a8d589 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -3882,6 +3882,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index eb659fda3be9..5f393eef5e17 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -15424,6 +15424,12 @@ }, "ResponseItemMetadata": { "properties": { + "source_call_id": { + "type": [ + "string", + "null" + ] + }, "turn_id": { "type": [ "string", @@ -17310,6 +17316,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index e0562760f6b7..e88c353fc705 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -11877,6 +11877,12 @@ }, "ResponseItemMetadata": { "properties": { + "source_call_id": { + "type": [ + "string", + "null" + ] + }, "turn_id": { "type": [ "string", @@ -15118,6 +15124,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json index 233ff8dc98e2..abcd3dfea10e 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json @@ -605,6 +605,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json index a55d9e776649..c5b9c6d6cc69 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json @@ -605,6 +605,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json index fdd40dae6106..3efca032240f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json @@ -1035,6 +1035,12 @@ }, "ResponseItemMetadata": { "properties": { + "source_call_id": { + "type": [ + "string", + "null" + ] + }, "turn_id": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json index e27c911b3600..581faea94d79 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -749,6 +749,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 4a666672b66e..4c437356dc4d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -1233,6 +1233,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index c1d1b6759a80..e9182b6affa9 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -1048,6 +1048,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json index 801d1cf131be..d5315b32e8f7 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -1048,6 +1048,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index 01b5ed907efc..feb6e6a17a95 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -1048,6 +1048,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json index 7ba2a0a64696..093c27044d26 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json @@ -1106,6 +1106,12 @@ }, "ResponseItemMetadata": { "properties": { + "source_call_id": { + "type": [ + "string", + "null" + ] + }, "turn_id": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index 032d1dd74ef2..b1b44ce098e4 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -1233,6 +1233,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index c1ad534756fd..ba57cee7d375 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -1048,6 +1048,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 33373b9f7208..8166de5d36d5 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -1233,6 +1233,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index 577df57e0501..8cd99a79f8dd 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -1048,6 +1048,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index fcc42e6261c0..a16ffadc3af4 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -1048,6 +1048,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json index 24d1dccf475c..3fdf06f8f92e 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -749,6 +749,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json index da88eacd4874..bd8e18c7d176 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -749,6 +749,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json index edb3fa636a07..84e32c7d14b8 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -749,6 +749,59 @@ "title": "AgentMessageThreadItem", "type": "object" }, + { + "properties": { + "content": { + "type": "string" + }, + "encrypted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "otherReceivers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "sourceCallId": { + "type": [ + "string", + "null" + ] + }, + "triggerTurn": { + "type": "boolean" + }, + "type": { + "enum": [ + "interAgentCommunication" + ], + "title": "InterAgentCommunicationThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "encrypted", + "id", + "otherReceivers", + "receiver", + "sender", + "triggerTurn", + "type" + ], + "title": "InterAgentCommunicationThreadItem", + "type": "object" + }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/typescript/ResponseItemMetadata.ts b/codex-rs/app-server-protocol/schema/typescript/ResponseItemMetadata.ts index f7b69a6dfe17..365c561fd15b 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ResponseItemMetadata.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ResponseItemMetadata.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type ResponseItemMetadata = { turn_id?: string, }; +export type ResponseItemMetadata = { turn_id?: string, source_call_id?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts index 4ccab77b32ad..933f6b632e2a 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts @@ -24,7 +24,7 @@ import type { SubAgentActivityKind } from "./SubAgentActivityKind"; import type { UserInput } from "./UserInput"; import type { WebSearchAction } from "./WebSearchAction"; -export type ThreadItem = { "type": "userMessage", id: string, clientId: string | null, content: Array, } | { "type": "hookPrompt", id: string, fragments: Array, } | { "type": "agentMessage", id: string, text: string, phase: MessagePhase | null, memoryCitation: MemoryCitation | null, } | { "type": "plan", id: string, text: string, } | { "type": "reasoning", id: string, summary: Array, content: Array, } | { "type": "commandExecution", id: string, +export type ThreadItem = { "type": "userMessage", id: string, clientId: string | null, content: Array, } | { "type": "hookPrompt", id: string, fragments: Array, } | { "type": "agentMessage", id: string, text: string, phase: MessagePhase | null, memoryCitation: MemoryCitation | null, } | { "type": "interAgentCommunication", id: string, sourceCallId: string | null, sender: string, receiver: string, otherReceivers: Array, content: string, encrypted: boolean, triggerTurn: boolean, } | { "type": "plan", id: string, text: string, } | { "type": "reasoning", id: string, summary: Array, content: Array, } | { "type": "commandExecution", id: string, /** * The command to be executed. */ diff --git a/codex-rs/app-server-protocol/src/protocol/thread_history.rs b/codex-rs/app-server-protocol/src/protocol/thread_history.rs index 6ca038981fc1..4f942c1a9fe1 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -376,6 +376,7 @@ impl ThreadHistoryBuilder { codex_protocol::items::TurnItem::UserMessage(_) | codex_protocol::items::TurnItem::HookPrompt(_) | codex_protocol::items::TurnItem::AgentMessage(_) + | codex_protocol::items::TurnItem::InterAgentCommunication(_) | codex_protocol::items::TurnItem::Reasoning(_) | codex_protocol::items::TurnItem::WebSearch(_) | codex_protocol::items::TurnItem::ImageView(_) @@ -403,6 +404,12 @@ impl ThreadHistoryBuilder { ThreadItem::from(payload.item.clone()), ); } + codex_protocol::items::TurnItem::InterAgentCommunication(_) => { + self.upsert_item_in_turn_id( + &payload.turn_id, + ThreadItem::from(payload.item.clone()), + ); + } codex_protocol::items::TurnItem::UserMessage(_) | codex_protocol::items::TurnItem::HookPrompt(_) | codex_protocol::items::TurnItem::AgentMessage(_) diff --git a/codex-rs/app-server-protocol/src/protocol/v2/item.rs b/codex-rs/app-server-protocol/src/protocol/v2/item.rs index 502eb62e103a..9e265f3e0fa1 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/item.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/item.rs @@ -237,6 +237,18 @@ pub enum ThreadItem { }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] + InterAgentCommunication { + id: String, + source_call_id: Option, + sender: String, + receiver: String, + other_receivers: Vec, + content: String, + encrypted: bool, + trigger_turn: bool, + }, + #[serde(rename_all = "camelCase")] + #[ts(rename_all = "camelCase")] /// EXPERIMENTAL - proposed plan item content. The completed plan item is /// authoritative and may not match the concatenation of `PlanDelta` text. Plan { id: String, text: String }, @@ -397,6 +409,7 @@ impl ThreadItem { ThreadItem::UserMessage { id, .. } | ThreadItem::HookPrompt { id, .. } | ThreadItem::AgentMessage { id, .. } + | ThreadItem::InterAgentCommunication { id, .. } | ThreadItem::Plan { id, .. } | ThreadItem::Reasoning { id, .. } | ThreadItem::CommandExecution { id, .. } @@ -827,6 +840,22 @@ impl From for ThreadItem { memory_citation: agent.memory_citation.map(Into::into), } } + CoreTurnItem::InterAgentCommunication(communication) => { + ThreadItem::InterAgentCommunication { + id: communication.id, + source_call_id: communication.source_call_id, + sender: communication.sender.to_string(), + receiver: communication.receiver.to_string(), + other_receivers: communication + .other_receivers + .into_iter() + .map(String::from) + .collect(), + content: communication.content, + encrypted: communication.encrypted, + trigger_turn: communication.trigger_turn, + } + } CoreTurnItem::Plan(plan) => ThreadItem::Plan { id: plan.id, text: plan.text, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index 2567e386c839..6a3ce66f975e 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -1,9 +1,11 @@ use super::*; +use codex_protocol::AgentPath; use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest; use codex_protocol::items::AgentMessageContent; use codex_protocol::items::AgentMessageItem; use codex_protocol::items::FileChangeItem; use codex_protocol::items::ImageViewItem; +use codex_protocol::items::InterAgentCommunicationItem; use codex_protocol::items::McpToolCallItem; use codex_protocol::items::McpToolCallStatus as CoreMcpToolCallStatus; use codex_protocol::items::ReasoningItem; @@ -2496,6 +2498,35 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { } ); + let communication_item = TurnItem::InterAgentCommunication(InterAgentCommunicationItem { + id: "communication-1".to_string(), + source_call_id: Some("call-1".to_string()), + sender: AgentPath::root(), + receiver: AgentPath::root().join("worker").expect("receiver path"), + other_receivers: vec![ + AgentPath::root() + .join("reviewer") + .expect("other receiver path"), + ], + content: "please check this".to_string(), + encrypted: true, + trigger_turn: false, + }); + + assert_eq!( + ThreadItem::from(communication_item), + ThreadItem::InterAgentCommunication { + id: "communication-1".to_string(), + source_call_id: Some("call-1".to_string()), + sender: "/root".to_string(), + receiver: "/root/worker".to_string(), + other_receivers: vec!["/root/reviewer".to_string()], + content: "please check this".to_string(), + encrypted: true, + trigger_turn: false, + } + ); + let reasoning_item = TurnItem::Reasoning(ReasoningItem { id: "reasoning-1".to_string(), summary_text: vec!["line one".to_string(), "line two".to_string()], diff --git a/codex-rs/core/src/compact_tests.rs b/codex-rs/core/src/compact_tests.rs index a52e70a7ba1e..c1004004ade2 100644 --- a/codex-rs/core/src/compact_tests.rs +++ b/codex-rs/core/src/compact_tests.rs @@ -238,6 +238,7 @@ fn build_compacted_history_preserves_user_message_metadata() { message: "first user message".to_string(), metadata: Some(ResponseItemMetadata { turn_id: Some("turn-1".to_string()), + ..Default::default() }), }], "summary text", diff --git a/codex-rs/core/src/context_manager/history_tests.rs b/codex-rs/core/src/context_manager/history_tests.rs index a98061b5b097..9a449653c294 100644 --- a/codex-rs/core/src/context_manager/history_tests.rs +++ b/codex-rs/core/src/context_manager/history_tests.rs @@ -1049,6 +1049,7 @@ fn record_items_truncates_function_call_output_content() { }, metadata: Some(ResponseItemMetadata { turn_id: Some("turn-1".to_string()), + ..Default::default() }), }; diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 242e35f37568..a7262cf48c92 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -93,6 +93,7 @@ use codex_protocol::config_types::Settings; use codex_protocol::config_types::WebSearchMode; use codex_protocol::dynamic_tools::DynamicToolResponse; use codex_protocol::dynamic_tools::DynamicToolSpec; +use codex_protocol::items::InterAgentCommunicationItem; use codex_protocol::items::TurnItem; use codex_protocol::items::UserMessageItem; use codex_protocol::mcp::CallToolResult; @@ -2674,6 +2675,26 @@ impl Session { communication: InterAgentCommunication, ) { let response_item = communication.to_model_input_item(); + let source_call_id = communication + .metadata + .as_ref() + .and_then(|metadata| metadata.source_call_id.clone()); + let (content, encrypted) = match &communication.encrypted_content { + Some(encrypted_content) => (encrypted_content.clone(), true), + None => (communication.content.clone(), false), + }; + let communication_item = InterAgentCommunicationItem { + id: source_call_id + .clone() + .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()), + source_call_id, + sender: communication.author.clone(), + receiver: communication.recipient.clone(), + other_receivers: communication.other_recipients.clone(), + content, + encrypted, + trigger_turn: communication.trigger_turn, + }; let items = self.prepare_conversation_items_for_history( turn_context, std::slice::from_ref(&response_item), @@ -2686,6 +2707,11 @@ impl Session { self.persist_rollout_items(&[RolloutItem::InterAgentCommunication(communication)]) .await; self.send_raw_response_items(turn_context, items).await; + self.emit_turn_item_completed( + turn_context, + TurnItem::InterAgentCommunication(communication_item), + ) + .await; } async fn maybe_warn_on_server_model_mismatch( diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 5086fd63bd57..1cd39aec6590 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -1606,6 +1606,7 @@ async fn reconstruct_history_uses_replacement_history_verbatim() { phase: None, metadata: Some(ResponseItemMetadata { turn_id: Some("compact-turn".to_string()), + ..Default::default() }), }; let replacement_history = vec![ diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs index 7fdb80e8bcb2..81eebed4a304 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs @@ -6,6 +6,7 @@ use super::*; use crate::tools::context::FunctionToolOutput; use crate::turn_timing::now_unix_timestamp_ms; +use codex_protocol::models::ResponseItemMetadata; use codex_protocol::protocol::InterAgentCommunication; #[derive(Clone, Copy, PartialEq, Eq)] @@ -99,8 +100,12 @@ pub(crate) async fn handle_message_string_tool( .session_source .get_agent_path() .unwrap_or_else(AgentPath::root); - let communication = + let mut communication = communication_from_tool_message(author, receiver_agent_path.clone(), message); + communication + .metadata + .get_or_insert_with(ResponseItemMetadata::default) + .source_call_id = Some(call_id.clone()); let result = session .services .agent_control diff --git a/codex-rs/protocol/src/items.rs b/codex-rs/protocol/src/items.rs index 01f44576ace3..d830ba300277 100644 --- a/codex-rs/protocol/src/items.rs +++ b/codex-rs/protocol/src/items.rs @@ -1,3 +1,4 @@ +use crate::AgentPath; use crate::mcp::CallToolResult; use crate::memory_citation::MemoryCitation; use crate::models::ContentItem; @@ -43,6 +44,7 @@ pub enum TurnItem { UserMessage(UserMessageItem), HookPrompt(HookPromptItem), AgentMessage(AgentMessageItem), + InterAgentCommunication(InterAgentCommunicationItem), Plan(PlanItem), Reasoning(ReasoningItem), WebSearch(WebSearchItem), @@ -114,6 +116,23 @@ pub struct AgentMessageItem { pub memory_citation: Option, } +#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +#[ts(rename_all = "camelCase")] +pub struct InterAgentCommunicationItem { + pub id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub source_call_id: Option, + pub sender: AgentPath, + pub receiver: AgentPath, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub other_receivers: Vec, + pub content: String, + pub encrypted: bool, + pub trigger_turn: bool, +} + #[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)] pub struct PlanItem { pub id: String, @@ -579,6 +598,7 @@ impl TurnItem { TurnItem::UserMessage(item) => item.id.clone(), TurnItem::HookPrompt(item) => item.id.clone(), TurnItem::AgentMessage(item) => item.id.clone(), + TurnItem::InterAgentCommunication(item) => item.id.clone(), TurnItem::Plan(item) => item.id.clone(), TurnItem::Reasoning(item) => item.id.clone(), TurnItem::WebSearch(item) => item.id.clone(), @@ -596,6 +616,7 @@ impl TurnItem { TurnItem::UserMessage(item) => vec![item.as_legacy_event()], TurnItem::HookPrompt(_) => Vec::new(), TurnItem::AgentMessage(item) => item.as_legacy_events(), + TurnItem::InterAgentCommunication(_) => Vec::new(), TurnItem::Plan(_) => Vec::new(), TurnItem::WebSearch(item) => vec![item.as_legacy_event()], TurnItem::ImageView(item) => { diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 0e7e9bb44655..9443866c6f0e 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -909,6 +909,9 @@ pub struct ResponseItemMetadata { #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub turn_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub source_call_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)] @@ -2072,6 +2075,7 @@ mod tests { fn response_item_metadata(turn_id: &str) -> ResponseItemMetadata { ResponseItemMetadata { turn_id: Some(turn_id.to_string()), + ..Default::default() } } From 0979d99c386c89d03ed9e1cf7e0a4bc7f25ed5b6 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 16 Jun 2026 20:21:56 +0200 Subject: [PATCH 2/6] Handle inter-agent items in TUI consumers --- codex-rs/tui/src/app/agent_status_feed.rs | 11 ++++ .../tui/src/app/agent_status_feed_tests.rs | 58 +++++++++++++++++++ codex-rs/tui/src/chatwidget/replay.rs | 1 + codex-rs/tui/src/thread_transcript.rs | 1 + 4 files changed, 71 insertions(+) diff --git a/codex-rs/tui/src/app/agent_status_feed.rs b/codex-rs/tui/src/app/agent_status_feed.rs index 544c0b7172d5..f9dd137020f6 100644 --- a/codex-rs/tui/src/app/agent_status_feed.rs +++ b/codex-rs/tui/src/app/agent_status_feed.rs @@ -135,6 +135,17 @@ impl AgentStatusThreadPreview { fn activity_summary(item: &ThreadItem) -> Option { let summary = match item { ThreadItem::AgentMessage { text, .. } | ThreadItem::Plan { text, .. } => text, + ThreadItem::InterAgentCommunication { + sender, + content, + encrypted, + .. + } => { + if *encrypted { + return bounded_summary(&format!("Encrypted message from {sender}")); + } + return bounded_summary(&format!("Message from {sender}: {content}")); + } ThreadItem::Reasoning { summary, .. } => summary.last()?, ThreadItem::CommandExecution { command, .. } => { let command = truncate_text( diff --git a/codex-rs/tui/src/app/agent_status_feed_tests.rs b/codex-rs/tui/src/app/agent_status_feed_tests.rs index 5b8bc4b59b17..c4a671463777 100644 --- a/codex-rs/tui/src/app/agent_status_feed_tests.rs +++ b/codex-rs/tui/src/app/agent_status_feed_tests.rs @@ -60,6 +60,64 @@ fn agent_status_uses_bounded_buffered_activity() { assert!(!rendered.contains("unbounded output")); } +#[test] +fn agent_status_summarizes_inter_agent_communications() { + let mut store = ThreadEventStore::new(/*capacity*/ 8); + store.push_notification(ServerNotification::ItemCompleted( + ItemCompletedNotification { + item: ThreadItem::InterAgentCommunication { + id: "message-1".to_string(), + source_call_id: Some("call-1".to_string()), + sender: "/root".to_string(), + receiver: "/root/reviewer".to_string(), + other_receivers: Vec::new(), + content: "Please check the schema output.".to_string(), + encrypted: false, + trigger_turn: true, + }, + thread_id: "thread-child".to_string(), + turn_id: "turn-1".to_string(), + completed_at_ms: 1, + }, + )); + store.push_notification(ServerNotification::ItemCompleted( + ItemCompletedNotification { + item: ThreadItem::InterAgentCommunication { + id: "message-2".to_string(), + source_call_id: Some("call-2".to_string()), + sender: "/root/other".to_string(), + receiver: "/root/reviewer".to_string(), + other_receivers: Vec::new(), + content: "opaque-ciphertext".to_string(), + encrypted: true, + trigger_turn: false, + }, + thread_id: "thread-child".to_string(), + turn_id: "turn-1".to_string(), + completed_at_ms: 2, + }, + )); + + let preview = AgentStatusThreadPreview::from_store("/root/reviewer".to_string(), &store); + let cell = AgentStatusHistoryCell::new(vec![preview]); + let rendered = cell + .display_lines(/*width*/ 80) + .iter() + .map(ToString::to_string) + .collect::>() + .join("\n"); + + insta::assert_snapshot!(rendered, @r###" + /agent + Sub-agents running + + • `/root/reviewer` + Message from /root: Please check the schema output. + Encrypted message from /root/other + "###); + assert!(!rendered.contains("opaque-ciphertext")); +} + #[test] fn agent_status_uses_reasoning_summaries_only() { let mut store = ThreadEventStore::new(/*capacity*/ 8); diff --git a/codex-rs/tui/src/chatwidget/replay.rs b/codex-rs/tui/src/chatwidget/replay.rs index a96ab614cac9..de60eec42993 100644 --- a/codex-rs/tui/src/chatwidget/replay.rs +++ b/codex-rs/tui/src/chatwidget/replay.rs @@ -192,6 +192,7 @@ impl ChatWidget { agents_states, }), item @ ThreadItem::SubAgentActivity { .. } => self.on_sub_agent_activity(item), + ThreadItem::InterAgentCommunication { .. } => {} ThreadItem::DynamicToolCall { .. } => {} ThreadItem::Sleep { .. } => {} } diff --git a/codex-rs/tui/src/thread_transcript.rs b/codex-rs/tui/src/thread_transcript.rs index 13b2a3a3897e..f127c3985802 100644 --- a/codex-rs/tui/src/thread_transcript.rs +++ b/codex-rs/tui/src/thread_transcript.rs @@ -225,6 +225,7 @@ fn fallback_transcript_cell(item: &ThreadItem) -> Option { } ThreadItem::UserMessage { .. } | ThreadItem::AgentMessage { .. } + | ThreadItem::InterAgentCommunication { .. } | ThreadItem::Plan { .. } | ThreadItem::Reasoning { .. } | ThreadItem::Sleep { .. } => return None, From bf64dcab2cda3bc91a752cb7e634a73494e4e3a1 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 16 Jun 2026 20:53:48 +0200 Subject: [PATCH 3/6] Simplify inter-agent communication joins --- codex-rs/analytics/src/reducer.rs | 2 - .../schema/json/ServerNotification.json | 53 ----------------- .../codex_app_server_protocol.schemas.json | 53 ----------------- .../codex_app_server_protocol.v2.schemas.json | 53 ----------------- .../json/v2/ItemCompletedNotification.json | 53 ----------------- .../json/v2/ItemStartedNotification.json | 53 ----------------- .../schema/json/v2/ReviewStartResponse.json | 53 ----------------- .../schema/json/v2/ThreadForkResponse.json | 53 ----------------- .../schema/json/v2/ThreadListResponse.json | 53 ----------------- .../json/v2/ThreadMetadataUpdateResponse.json | 53 ----------------- .../schema/json/v2/ThreadReadResponse.json | 53 ----------------- .../schema/json/v2/ThreadResumeResponse.json | 53 ----------------- .../json/v2/ThreadRollbackResponse.json | 53 ----------------- .../schema/json/v2/ThreadStartResponse.json | 53 ----------------- .../json/v2/ThreadStartedNotification.json | 53 ----------------- .../json/v2/ThreadUnarchiveResponse.json | 53 ----------------- .../json/v2/TurnCompletedNotification.json | 53 ----------------- .../schema/json/v2/TurnStartResponse.json | 53 ----------------- .../json/v2/TurnStartedNotification.json | 53 ----------------- .../schema/typescript/v2/ThreadItem.ts | 2 +- .../src/protocol/thread_history.rs | 7 --- .../src/protocol/v2/item.rs | 29 ---------- .../src/protocol/v2/tests.rs | 31 ---------- codex-rs/core/src/session/mod.rs | 26 --------- .../src/tools/handlers/multi_agents_tests.rs | 27 +++++++++ .../tools/handlers/multi_agents_v2/spawn.rs | 7 ++- codex-rs/protocol/src/items.rs | 21 ------- codex-rs/tui/src/app/agent_status_feed.rs | 11 ---- .../tui/src/app/agent_status_feed_tests.rs | 58 ------------------- codex-rs/tui/src/chatwidget/replay.rs | 1 - codex-rs/tui/src/thread_transcript.rs | 1 - 31 files changed, 34 insertions(+), 1143 deletions(-) diff --git a/codex-rs/analytics/src/reducer.rs b/codex-rs/analytics/src/reducer.rs index 1123af7d67f6..89d6e57dc5c4 100644 --- a/codex-rs/analytics/src/reducer.rs +++ b/codex-rs/analytics/src/reducer.rs @@ -381,7 +381,6 @@ impl TurnToolCounts { ThreadItem::UserMessage { .. } | ThreadItem::HookPrompt { .. } | ThreadItem::AgentMessage { .. } - | ThreadItem::InterAgentCommunication { .. } | ThreadItem::Plan { .. } | ThreadItem::Reasoning { .. } | ThreadItem::ImageView { .. } @@ -1618,7 +1617,6 @@ fn tracked_tool_item_id(item: &ThreadItem) -> Option<&str> { ThreadItem::UserMessage { .. } | ThreadItem::HookPrompt { .. } | ThreadItem::AgentMessage { .. } - | ThreadItem::InterAgentCommunication { .. } | ThreadItem::Plan { .. } | ThreadItem::Reasoning { .. } | ThreadItem::SubAgentActivity { .. } diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 59d753a8d589..1cf3d96c8172 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -3882,59 +3882,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 5f393eef5e17..0656368ae08b 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -17316,59 +17316,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index e88c353fc705..1b72355b6ab4 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -15124,59 +15124,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json index abcd3dfea10e..233ff8dc98e2 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json @@ -605,59 +605,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json index c5b9c6d6cc69..a55d9e776649 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json @@ -605,59 +605,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json index 581faea94d79..e27c911b3600 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -749,59 +749,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 4c437356dc4d..4a666672b66e 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -1233,59 +1233,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index e9182b6affa9..c1d1b6759a80 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -1048,59 +1048,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json index d5315b32e8f7..801d1cf131be 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -1048,59 +1048,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index feb6e6a17a95..01b5ed907efc 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -1048,59 +1048,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index b1b44ce098e4..032d1dd74ef2 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -1233,59 +1233,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index ba57cee7d375..c1ad534756fd 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -1048,59 +1048,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 8166de5d36d5..33373b9f7208 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -1233,59 +1233,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index 8cd99a79f8dd..577df57e0501 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -1048,59 +1048,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index a16ffadc3af4..fcc42e6261c0 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -1048,59 +1048,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json index 3fdf06f8f92e..24d1dccf475c 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -749,59 +749,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json index bd8e18c7d176..da88eacd4874 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -749,59 +749,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json index 84e32c7d14b8..edb3fa636a07 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -749,59 +749,6 @@ "title": "AgentMessageThreadItem", "type": "object" }, - { - "properties": { - "content": { - "type": "string" - }, - "encrypted": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "otherReceivers": { - "items": { - "type": "string" - }, - "type": "array" - }, - "receiver": { - "type": "string" - }, - "sender": { - "type": "string" - }, - "sourceCallId": { - "type": [ - "string", - "null" - ] - }, - "triggerTurn": { - "type": "boolean" - }, - "type": { - "enum": [ - "interAgentCommunication" - ], - "title": "InterAgentCommunicationThreadItemType", - "type": "string" - } - }, - "required": [ - "content", - "encrypted", - "id", - "otherReceivers", - "receiver", - "sender", - "triggerTurn", - "type" - ], - "title": "InterAgentCommunicationThreadItem", - "type": "object" - }, { "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts index 933f6b632e2a..4ccab77b32ad 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts @@ -24,7 +24,7 @@ import type { SubAgentActivityKind } from "./SubAgentActivityKind"; import type { UserInput } from "./UserInput"; import type { WebSearchAction } from "./WebSearchAction"; -export type ThreadItem = { "type": "userMessage", id: string, clientId: string | null, content: Array, } | { "type": "hookPrompt", id: string, fragments: Array, } | { "type": "agentMessage", id: string, text: string, phase: MessagePhase | null, memoryCitation: MemoryCitation | null, } | { "type": "interAgentCommunication", id: string, sourceCallId: string | null, sender: string, receiver: string, otherReceivers: Array, content: string, encrypted: boolean, triggerTurn: boolean, } | { "type": "plan", id: string, text: string, } | { "type": "reasoning", id: string, summary: Array, content: Array, } | { "type": "commandExecution", id: string, +export type ThreadItem = { "type": "userMessage", id: string, clientId: string | null, content: Array, } | { "type": "hookPrompt", id: string, fragments: Array, } | { "type": "agentMessage", id: string, text: string, phase: MessagePhase | null, memoryCitation: MemoryCitation | null, } | { "type": "plan", id: string, text: string, } | { "type": "reasoning", id: string, summary: Array, content: Array, } | { "type": "commandExecution", id: string, /** * The command to be executed. */ diff --git a/codex-rs/app-server-protocol/src/protocol/thread_history.rs b/codex-rs/app-server-protocol/src/protocol/thread_history.rs index 4f942c1a9fe1..6ca038981fc1 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -376,7 +376,6 @@ impl ThreadHistoryBuilder { codex_protocol::items::TurnItem::UserMessage(_) | codex_protocol::items::TurnItem::HookPrompt(_) | codex_protocol::items::TurnItem::AgentMessage(_) - | codex_protocol::items::TurnItem::InterAgentCommunication(_) | codex_protocol::items::TurnItem::Reasoning(_) | codex_protocol::items::TurnItem::WebSearch(_) | codex_protocol::items::TurnItem::ImageView(_) @@ -404,12 +403,6 @@ impl ThreadHistoryBuilder { ThreadItem::from(payload.item.clone()), ); } - codex_protocol::items::TurnItem::InterAgentCommunication(_) => { - self.upsert_item_in_turn_id( - &payload.turn_id, - ThreadItem::from(payload.item.clone()), - ); - } codex_protocol::items::TurnItem::UserMessage(_) | codex_protocol::items::TurnItem::HookPrompt(_) | codex_protocol::items::TurnItem::AgentMessage(_) diff --git a/codex-rs/app-server-protocol/src/protocol/v2/item.rs b/codex-rs/app-server-protocol/src/protocol/v2/item.rs index 9e265f3e0fa1..502eb62e103a 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/item.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/item.rs @@ -237,18 +237,6 @@ pub enum ThreadItem { }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] - InterAgentCommunication { - id: String, - source_call_id: Option, - sender: String, - receiver: String, - other_receivers: Vec, - content: String, - encrypted: bool, - trigger_turn: bool, - }, - #[serde(rename_all = "camelCase")] - #[ts(rename_all = "camelCase")] /// EXPERIMENTAL - proposed plan item content. The completed plan item is /// authoritative and may not match the concatenation of `PlanDelta` text. Plan { id: String, text: String }, @@ -409,7 +397,6 @@ impl ThreadItem { ThreadItem::UserMessage { id, .. } | ThreadItem::HookPrompt { id, .. } | ThreadItem::AgentMessage { id, .. } - | ThreadItem::InterAgentCommunication { id, .. } | ThreadItem::Plan { id, .. } | ThreadItem::Reasoning { id, .. } | ThreadItem::CommandExecution { id, .. } @@ -840,22 +827,6 @@ impl From for ThreadItem { memory_citation: agent.memory_citation.map(Into::into), } } - CoreTurnItem::InterAgentCommunication(communication) => { - ThreadItem::InterAgentCommunication { - id: communication.id, - source_call_id: communication.source_call_id, - sender: communication.sender.to_string(), - receiver: communication.receiver.to_string(), - other_receivers: communication - .other_receivers - .into_iter() - .map(String::from) - .collect(), - content: communication.content, - encrypted: communication.encrypted, - trigger_turn: communication.trigger_turn, - } - } CoreTurnItem::Plan(plan) => ThreadItem::Plan { id: plan.id, text: plan.text, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index 6a3ce66f975e..2567e386c839 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -1,11 +1,9 @@ use super::*; -use codex_protocol::AgentPath; use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest; use codex_protocol::items::AgentMessageContent; use codex_protocol::items::AgentMessageItem; use codex_protocol::items::FileChangeItem; use codex_protocol::items::ImageViewItem; -use codex_protocol::items::InterAgentCommunicationItem; use codex_protocol::items::McpToolCallItem; use codex_protocol::items::McpToolCallStatus as CoreMcpToolCallStatus; use codex_protocol::items::ReasoningItem; @@ -2498,35 +2496,6 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { } ); - let communication_item = TurnItem::InterAgentCommunication(InterAgentCommunicationItem { - id: "communication-1".to_string(), - source_call_id: Some("call-1".to_string()), - sender: AgentPath::root(), - receiver: AgentPath::root().join("worker").expect("receiver path"), - other_receivers: vec![ - AgentPath::root() - .join("reviewer") - .expect("other receiver path"), - ], - content: "please check this".to_string(), - encrypted: true, - trigger_turn: false, - }); - - assert_eq!( - ThreadItem::from(communication_item), - ThreadItem::InterAgentCommunication { - id: "communication-1".to_string(), - source_call_id: Some("call-1".to_string()), - sender: "/root".to_string(), - receiver: "/root/worker".to_string(), - other_receivers: vec!["/root/reviewer".to_string()], - content: "please check this".to_string(), - encrypted: true, - trigger_turn: false, - } - ); - let reasoning_item = TurnItem::Reasoning(ReasoningItem { id: "reasoning-1".to_string(), summary_text: vec!["line one".to_string(), "line two".to_string()], diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index a7262cf48c92..242e35f37568 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -93,7 +93,6 @@ use codex_protocol::config_types::Settings; use codex_protocol::config_types::WebSearchMode; use codex_protocol::dynamic_tools::DynamicToolResponse; use codex_protocol::dynamic_tools::DynamicToolSpec; -use codex_protocol::items::InterAgentCommunicationItem; use codex_protocol::items::TurnItem; use codex_protocol::items::UserMessageItem; use codex_protocol::mcp::CallToolResult; @@ -2675,26 +2674,6 @@ impl Session { communication: InterAgentCommunication, ) { let response_item = communication.to_model_input_item(); - let source_call_id = communication - .metadata - .as_ref() - .and_then(|metadata| metadata.source_call_id.clone()); - let (content, encrypted) = match &communication.encrypted_content { - Some(encrypted_content) => (encrypted_content.clone(), true), - None => (communication.content.clone(), false), - }; - let communication_item = InterAgentCommunicationItem { - id: source_call_id - .clone() - .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()), - source_call_id, - sender: communication.author.clone(), - receiver: communication.recipient.clone(), - other_receivers: communication.other_recipients.clone(), - content, - encrypted, - trigger_turn: communication.trigger_turn, - }; let items = self.prepare_conversation_items_for_history( turn_context, std::slice::from_ref(&response_item), @@ -2707,11 +2686,6 @@ impl Session { self.persist_rollout_items(&[RolloutItem::InterAgentCommunication(communication)]) .await; self.send_raw_response_items(turn_context, items).await; - self.emit_turn_item_completed( - turn_context, - TurnItem::InterAgentCommunication(communication_item), - ) - .await; } async fn maybe_warn_on_server_model_mismatch( diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index ebb452c1de08..7b541b7cf9b0 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -1195,6 +1195,11 @@ async fn multi_agent_v2_spawn_returns_path_and_send_message_accepts_relative_pat && communication.other_recipients.is_empty() && communication.content.is_empty() && communication.encrypted_content.as_deref() == Some("encrypted-spawn-message") + && communication + .metadata + .as_ref() + .and_then(|metadata| metadata.source_call_id.as_deref()) + == Some("call-1") && communication.trigger_turn ) })); @@ -1222,6 +1227,11 @@ async fn multi_agent_v2_spawn_returns_path_and_send_message_accepts_relative_pat && communication.other_recipients.is_empty() && communication.content.is_empty() && communication.encrypted_content.as_deref() == Some("encrypted-send-message") + && communication + .metadata + .as_ref() + .and_then(|metadata| metadata.source_call_id.as_deref()) + == Some("call-1") && !communication.trigger_turn ) })); @@ -2027,6 +2037,23 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() .await .expect("followup_task should succeed"); + assert!(manager.captured_ops().iter().any(|(id, op)| { + *id == agent_id + && matches!( + op, + Op::InterAgentCommunication { communication } + if communication.author == AgentPath::root() + && communication.recipient == worker_path + && communication.encrypted_content.as_deref() == Some("continue") + && communication + .metadata + .as_ref() + .and_then(|metadata| metadata.source_call_id.as_deref()) + == Some("call-1") + && communication.trigger_turn + ) + })); + let second_turn = thread.codex.session.new_default_turn().await; thread .codex diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs index 2186a4a6d605..51813c5c1991 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs @@ -8,6 +8,7 @@ use crate::tools::handlers::multi_agents_spec::SpawnAgentToolOptions; use crate::tools::handlers::multi_agents_spec::create_spawn_agent_tool_v2; use crate::turn_timing::now_unix_timestamp_ms; use codex_protocol::AgentPath; +use codex_protocol::models::ResponseItemMetadata; use codex_protocol::protocol::Op; use codex_tools::ToolSpec; @@ -117,8 +118,12 @@ async fn handle_spawn_agent( .session_source .get_agent_path() .unwrap_or_else(AgentPath::root); - let communication = + let mut communication = communication_from_tool_message(author, new_agent_path.clone(), message); + communication + .metadata + .get_or_insert_with(ResponseItemMetadata::default) + .source_call_id = Some(call_id.clone()); Op::InterAgentCommunication { communication } } initial_operation => initial_operation, diff --git a/codex-rs/protocol/src/items.rs b/codex-rs/protocol/src/items.rs index d830ba300277..01f44576ace3 100644 --- a/codex-rs/protocol/src/items.rs +++ b/codex-rs/protocol/src/items.rs @@ -1,4 +1,3 @@ -use crate::AgentPath; use crate::mcp::CallToolResult; use crate::memory_citation::MemoryCitation; use crate::models::ContentItem; @@ -44,7 +43,6 @@ pub enum TurnItem { UserMessage(UserMessageItem), HookPrompt(HookPromptItem), AgentMessage(AgentMessageItem), - InterAgentCommunication(InterAgentCommunicationItem), Plan(PlanItem), Reasoning(ReasoningItem), WebSearch(WebSearchItem), @@ -116,23 +114,6 @@ pub struct AgentMessageItem { pub memory_citation: Option, } -#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -#[ts(rename_all = "camelCase")] -pub struct InterAgentCommunicationItem { - pub id: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub source_call_id: Option, - pub sender: AgentPath, - pub receiver: AgentPath, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub other_receivers: Vec, - pub content: String, - pub encrypted: bool, - pub trigger_turn: bool, -} - #[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)] pub struct PlanItem { pub id: String, @@ -598,7 +579,6 @@ impl TurnItem { TurnItem::UserMessage(item) => item.id.clone(), TurnItem::HookPrompt(item) => item.id.clone(), TurnItem::AgentMessage(item) => item.id.clone(), - TurnItem::InterAgentCommunication(item) => item.id.clone(), TurnItem::Plan(item) => item.id.clone(), TurnItem::Reasoning(item) => item.id.clone(), TurnItem::WebSearch(item) => item.id.clone(), @@ -616,7 +596,6 @@ impl TurnItem { TurnItem::UserMessage(item) => vec![item.as_legacy_event()], TurnItem::HookPrompt(_) => Vec::new(), TurnItem::AgentMessage(item) => item.as_legacy_events(), - TurnItem::InterAgentCommunication(_) => Vec::new(), TurnItem::Plan(_) => Vec::new(), TurnItem::WebSearch(item) => vec![item.as_legacy_event()], TurnItem::ImageView(item) => { diff --git a/codex-rs/tui/src/app/agent_status_feed.rs b/codex-rs/tui/src/app/agent_status_feed.rs index f9dd137020f6..544c0b7172d5 100644 --- a/codex-rs/tui/src/app/agent_status_feed.rs +++ b/codex-rs/tui/src/app/agent_status_feed.rs @@ -135,17 +135,6 @@ impl AgentStatusThreadPreview { fn activity_summary(item: &ThreadItem) -> Option { let summary = match item { ThreadItem::AgentMessage { text, .. } | ThreadItem::Plan { text, .. } => text, - ThreadItem::InterAgentCommunication { - sender, - content, - encrypted, - .. - } => { - if *encrypted { - return bounded_summary(&format!("Encrypted message from {sender}")); - } - return bounded_summary(&format!("Message from {sender}: {content}")); - } ThreadItem::Reasoning { summary, .. } => summary.last()?, ThreadItem::CommandExecution { command, .. } => { let command = truncate_text( diff --git a/codex-rs/tui/src/app/agent_status_feed_tests.rs b/codex-rs/tui/src/app/agent_status_feed_tests.rs index c4a671463777..5b8bc4b59b17 100644 --- a/codex-rs/tui/src/app/agent_status_feed_tests.rs +++ b/codex-rs/tui/src/app/agent_status_feed_tests.rs @@ -60,64 +60,6 @@ fn agent_status_uses_bounded_buffered_activity() { assert!(!rendered.contains("unbounded output")); } -#[test] -fn agent_status_summarizes_inter_agent_communications() { - let mut store = ThreadEventStore::new(/*capacity*/ 8); - store.push_notification(ServerNotification::ItemCompleted( - ItemCompletedNotification { - item: ThreadItem::InterAgentCommunication { - id: "message-1".to_string(), - source_call_id: Some("call-1".to_string()), - sender: "/root".to_string(), - receiver: "/root/reviewer".to_string(), - other_receivers: Vec::new(), - content: "Please check the schema output.".to_string(), - encrypted: false, - trigger_turn: true, - }, - thread_id: "thread-child".to_string(), - turn_id: "turn-1".to_string(), - completed_at_ms: 1, - }, - )); - store.push_notification(ServerNotification::ItemCompleted( - ItemCompletedNotification { - item: ThreadItem::InterAgentCommunication { - id: "message-2".to_string(), - source_call_id: Some("call-2".to_string()), - sender: "/root/other".to_string(), - receiver: "/root/reviewer".to_string(), - other_receivers: Vec::new(), - content: "opaque-ciphertext".to_string(), - encrypted: true, - trigger_turn: false, - }, - thread_id: "thread-child".to_string(), - turn_id: "turn-1".to_string(), - completed_at_ms: 2, - }, - )); - - let preview = AgentStatusThreadPreview::from_store("/root/reviewer".to_string(), &store); - let cell = AgentStatusHistoryCell::new(vec![preview]); - let rendered = cell - .display_lines(/*width*/ 80) - .iter() - .map(ToString::to_string) - .collect::>() - .join("\n"); - - insta::assert_snapshot!(rendered, @r###" - /agent - Sub-agents running - - • `/root/reviewer` - Message from /root: Please check the schema output. - Encrypted message from /root/other - "###); - assert!(!rendered.contains("opaque-ciphertext")); -} - #[test] fn agent_status_uses_reasoning_summaries_only() { let mut store = ThreadEventStore::new(/*capacity*/ 8); diff --git a/codex-rs/tui/src/chatwidget/replay.rs b/codex-rs/tui/src/chatwidget/replay.rs index de60eec42993..a96ab614cac9 100644 --- a/codex-rs/tui/src/chatwidget/replay.rs +++ b/codex-rs/tui/src/chatwidget/replay.rs @@ -192,7 +192,6 @@ impl ChatWidget { agents_states, }), item @ ThreadItem::SubAgentActivity { .. } => self.on_sub_agent_activity(item), - ThreadItem::InterAgentCommunication { .. } => {} ThreadItem::DynamicToolCall { .. } => {} ThreadItem::Sleep { .. } => {} } diff --git a/codex-rs/tui/src/thread_transcript.rs b/codex-rs/tui/src/thread_transcript.rs index f127c3985802..13b2a3a3897e 100644 --- a/codex-rs/tui/src/thread_transcript.rs +++ b/codex-rs/tui/src/thread_transcript.rs @@ -225,7 +225,6 @@ fn fallback_transcript_cell(item: &ThreadItem) -> Option { } ThreadItem::UserMessage { .. } | ThreadItem::AgentMessage { .. } - | ThreadItem::InterAgentCommunication { .. } | ThreadItem::Plan { .. } | ThreadItem::Reasoning { .. } | ThreadItem::Sleep { .. } => return None, From add04908ce88015164a9978547002cc37d1be9ff Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 16 Jun 2026 21:06:55 +0200 Subject: [PATCH 4/6] Update MAv2 spawn notification expectation --- codex-rs/core/tests/suite/subagent_notifications.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codex-rs/core/tests/suite/subagent_notifications.rs b/codex-rs/core/tests/suite/subagent_notifications.rs index 764380be7190..2501aa0dde31 100644 --- a/codex-rs/core/tests/suite/subagent_notifications.rs +++ b/codex-rs/core/tests/suite/subagent_notifications.rs @@ -1088,6 +1088,9 @@ async fn encrypted_multi_agent_v2_spawn_sends_agent_message_to_child() -> Result "type": "agent_message", "author": "/root", "recipient": "/root/worker", + "metadata": { + "source_call_id": SPAWN_CALL_ID, + }, "content": [ { "type": "input_text", From 3e657a4eec5ca68a86aa55dd9162ffd0f3643330 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 17 Jun 2026 13:37:56 +0200 Subject: [PATCH 5/6] Preserve MAv2 join keys across resume --- .../src/protocol/v2/thread.rs | 5 ++ codex-rs/app-server/README.md | 4 + .../request_processors/thread_processor.rs | 9 +- .../thread_processor_tests.rs | 1 + .../tests/suite/v2/thread_resume.rs | 55 ++++++++++++ codex-rs/core/src/session/mod.rs | 31 ++++++- codex-rs/core/src/tasks/mod.rs | 6 ++ .../src/tools/handlers/multi_agents_tests.rs | 85 ++++++++++++++----- .../tests/suite/subagent_notifications.rs | 3 + 9 files changed, 176 insertions(+), 23 deletions(-) diff --git a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs index 0252c4b048ae..4bb886a33f9c 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs @@ -359,6 +359,11 @@ pub struct ThreadResumeParams { #[experimental("thread/resume.initialTurnsPage")] #[ts(optional = nullable)] pub initial_turns_page: Option, + /// If true, opt into emitting raw Responses API items on the event stream. + /// This is for internal use only (e.g. Codex Cloud). + #[experimental("thread/resume.experimentalRawEvents")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub experimental_raw_events: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 336f769d7155..0d1397e7989f 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -1283,6 +1283,10 @@ Recoverable configuration and initialization warnings use the existing `configWa Generic runtime warnings use the `warning` notification: `{ threadId?, message }`. App-server emits this for non-fatal warnings from the core event stream, including cases where not all enabled skills are included in the model-visible skills list for a session. +### Raw response items (experimental) + +Set `experimentalRawEvents: true` on `thread/start` or `thread/resume` to receive `rawResponseItem/completed` notifications. Multi-agent v2 `agent_message` items include `metadata.source_call_id`, which matches the originating `spawn_agent`, `send_message`, or `followup_task` call id. Clients can use that field to join the raw message to the corresponding tool or activity event. + ### Notification opt-out Clients can suppress specific notifications per connection by sending exact method names in `initialize.params.capabilities.optOutNotificationMethods`. diff --git a/codex-rs/app-server/src/request_processors/thread_processor.rs b/codex-rs/app-server/src/request_processors/thread_processor.rs index 09ff0dd6981f..fbecf10f6b0c 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor.rs @@ -2593,6 +2593,7 @@ impl ThreadRequestProcessor { personality, exclude_turns, initial_turns_page, + experimental_raw_events, } = params; let include_turns = !exclude_turns; @@ -2695,7 +2696,7 @@ impl ThreadRequestProcessor { self.ensure_conversation_listener( thread_id, request_id.connection_id, - /*raw_events_enabled*/ false, + experimental_raw_events, ) .await, thread_id, @@ -2969,6 +2970,12 @@ impl ThreadRequestProcessor { .thread_state_manager .thread_state(existing_thread_id) .await; + if params.experimental_raw_events { + thread_state + .lock() + .await + .set_experimental_raw_events(/*enabled*/ true); + } self.ensure_listener_task_running( existing_thread_id, existing_thread.clone(), diff --git a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs index 42711e857012..f30f04e76edc 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs @@ -751,6 +751,7 @@ mod thread_processor_behavior_tests { personality: None, exclude_turns: false, initial_turns_page: None, + experimental_raw_events: false, }; let config_snapshot = ThreadConfigSnapshot { model: "gpt-5".to_string(), diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index e67184bf01d3..18bcd0c5293d 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -3488,6 +3488,61 @@ async fn thread_resume_supports_history_and_overrides() -> Result<()> { Ok(()) } +#[tokio::test] +async fn thread_resume_can_enable_raw_events_after_restart() -> Result<()> { + let server = create_mock_responses_server_repeating_assistant("Done").await; + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; + + let RestartedThreadFixture { + mut mcp, thread_id, .. + } = start_materialized_thread_and_restart(codex_home.path(), "seed history").await?; + + let resume_id = mcp + .send_thread_resume_request(ThreadResumeParams { + thread_id: thread_id.clone(), + experimental_raw_events: true, + ..Default::default() + }) + .await?; + let resume_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(resume_id)), + ) + .await??; + to_response::(resume_resp)?; + + let turn_id = mcp + .send_turn_start_request(TurnStartParams { + thread_id: thread_id.clone(), + client_user_message_id: None, + input: vec![UserInput::Text { + text: "emit raw events".to_string(), + text_elements: Vec::new(), + }], + ..Default::default() + }) + .await?; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(turn_id)), + ) + .await??; + + let notification = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("rawResponseItem/completed"), + ) + .await??; + let parsed: ServerNotification = notification.try_into()?; + let ServerNotification::RawResponseItemCompleted(notification) = parsed else { + anyhow::bail!("expected rawResponseItem/completed notification"); + }; + assert_eq!(notification.thread_id, thread_id); + + Ok(()) +} + struct RestartedThreadFixture { mcp: TestAppServer, thread_id: String, diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 242e35f37568..2158c3085d1f 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -340,6 +340,7 @@ use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::LocalImagePreparation; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; +use codex_protocol::models::ResponseItemMetadata; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::protocol::ApplyPatchApprovalRequestEvent; use codex_protocol::protocol::AskForApproval; @@ -380,6 +381,24 @@ use codex_utils_absolute_path::AbsolutePathBuf; #[cfg(test)] use codex_utils_stream_parser::ProposedPlanSegment; +struct InterAgentCompletionSourceCallId(String); + +pub(crate) fn record_inter_agent_completion_source_call_id( + turn_context: &TurnContext, + communication: &InterAgentCommunication, +) { + if communication.trigger_turn + && let Some(source_call_id) = communication + .metadata + .as_ref() + .and_then(|metadata| metadata.source_call_id.as_ref()) + { + let _ = turn_context + .extension_data + .get_or_init(|| InterAgentCompletionSourceCallId(source_call_id.clone())); + } +} + /// The high-level interface to the Codex system. /// It operates as a queue pair where you send submissions and receive events. pub struct Codex { @@ -1773,13 +1792,22 @@ impl Session { .rollout_thread_trace .is_enabled() .then(|| message.clone()); - let communication = InterAgentCommunication::new( + let mut communication = InterAgentCommunication::new( child_agent_path.clone(), parent_agent_path, Vec::new(), message, /*trigger_turn*/ false, ); + if let Some(source_call_id) = turn_context + .extension_data + .get::() + { + communication.metadata = Some(ResponseItemMetadata { + source_call_id: Some(source_call_id.0.clone()), + ..Default::default() + }); + } if let Err(err) = self .services .agent_control @@ -2673,6 +2701,7 @@ impl Session { turn_context: &TurnContext, communication: InterAgentCommunication, ) { + record_inter_agent_completion_source_call_id(turn_context, &communication); let response_item = communication.to_model_input_item(); let items = self.prepare_conversation_items_for_history( turn_context, diff --git a/codex-rs/core/src/tasks/mod.rs b/codex-rs/core/src/tasks/mod.rs index b2f337ec42f2..17c2e1510583 100644 --- a/codex-rs/core/src/tasks/mod.rs +++ b/codex-rs/core/src/tasks/mod.rs @@ -29,6 +29,7 @@ use crate::hook_runtime::inspect_pending_input; use crate::hook_runtime::record_additional_contexts; use crate::hook_runtime::record_pending_input; use crate::session::TurnInput; +use crate::session::record_inter_agent_completion_source_call_id; use crate::session::session::Session; use crate::session::turn_context::TurnContext; use crate::state::ActiveTurn; @@ -345,6 +346,11 @@ impl Session { .clear_turn(&turn_context.sub_id); let pending_items = self.input_queue.get_pending_input(&self.active_turn).await; + for input_item in input.iter().chain(&pending_items) { + if let TurnInput::InterAgentCommunication(communication) = input_item { + record_inter_agent_completion_source_call_id(&turn_context, communication); + } + } let turn_state = { let mut active = self.active_turn.lock().await; let turn = active.get_or_insert_with(ActiveTurn::default); diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index 7b541b7cf9b0..5ef17df50507 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -2007,8 +2007,27 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() .await .expect("worker thread should exist"); let worker_path = AgentPath::try_from("/root/worker").expect("worker path"); + let spawn_communication = manager + .captured_ops() + .into_iter() + .find_map(|(id, op)| { + (id == agent_id).then_some(op).and_then(|op| match op { + Op::InterAgentCommunication { communication } + if communication.encrypted_content.as_deref() == Some("boot worker") => + { + Some(communication) + } + _ => None, + }) + }) + .expect("spawn communication should be captured"); let first_turn = thread.codex.session.new_default_turn().await; + thread + .codex + .session + .record_inter_agent_communication(first_turn.as_ref(), spawn_communication) + .await; thread .codex .session @@ -2024,23 +2043,26 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() ) .await; + let mut followup_invocation = invocation( + session, + turn, + "followup_task", + function_payload(json!({ + "target": agent_id.to_string(), + "message": "continue", + })), + ); + followup_invocation.call_id = "followup-call".to_string(); FollowupTaskHandlerV2 - .handle(invocation( - session, - turn, - "followup_task", - function_payload(json!({ - "target": agent_id.to_string(), - "message": "continue", - })), - )) + .handle(followup_invocation) .await .expect("followup_task should succeed"); - assert!(manager.captured_ops().iter().any(|(id, op)| { - *id == agent_id - && matches!( - op, + let followup_communication = manager + .captured_ops() + .into_iter() + .find_map(|(id, op)| { + (id == agent_id).then_some(op).and_then(|op| match op { Op::InterAgentCommunication { communication } if communication.author == AgentPath::root() && communication.recipient == worker_path @@ -2049,12 +2071,22 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() .metadata .as_ref() .and_then(|metadata| metadata.source_call_id.as_deref()) - == Some("call-1") - && communication.trigger_turn - ) - })); + == Some("followup-call") + && communication.trigger_turn => + { + Some(communication) + } + _ => None, + }) + }) + .expect("followup communication should be captured"); let second_turn = thread.codex.session.new_default_turn().await; + thread + .codex + .session + .record_inter_agent_communication(second_turn.as_ref(), followup_communication) + .await; thread .codex .session @@ -2098,7 +2130,12 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() && communication.other_recipients.is_empty() && !communication.trigger_turn => { - Some(communication.content) + Some(( + communication.content, + communication + .metadata + .and_then(|metadata| metadata.source_call_id), + )) } _ => None, }) @@ -2106,11 +2143,11 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() .collect::>(); let first_count = notifications .iter() - .filter(|message| **message == first_notification) + .filter(|(message, _)| *message == first_notification) .count(); let second_count = notifications .iter() - .filter(|message| **message == second_notification) + .filter(|(message, _)| *message == second_notification) .count(); if first_count == 1 && second_count == 1 { break notifications; @@ -2121,7 +2158,13 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() .await .expect("parent should receive one completion notification per child turn"); - assert_eq!(notifications.len(), 2); + assert_eq!( + notifications, + vec![ + (first_notification, Some("call-1".to_string())), + (second_notification, Some("followup-call".to_string())), + ] + ); } #[tokio::test] diff --git a/codex-rs/core/tests/suite/subagent_notifications.rs b/codex-rs/core/tests/suite/subagent_notifications.rs index 2501aa0dde31..d017ad477289 100644 --- a/codex-rs/core/tests/suite/subagent_notifications.rs +++ b/codex-rs/core/tests/suite/subagent_notifications.rs @@ -1234,6 +1234,9 @@ async fn plaintext_multi_agent_v2_completion_sends_agent_message( "type": "agent_message", "author": "/root/worker", "recipient": "/root", + "metadata": { + "source_call_id": SPAWN_CALL_ID, + }, "content": [{ "type": "input_text", "text": notification, From 11f62190426b0a3c590fa6c16718355aac1d87e7 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 17 Jun 2026 14:14:46 +0200 Subject: [PATCH 6/6] Keep raw join key changes minimal --- .../src/protocol/v2/thread.rs | 5 -- codex-rs/app-server/README.md | 4 - .../request_processors/thread_processor.rs | 9 +- .../thread_processor_tests.rs | 1 - .../tests/suite/v2/thread_resume.rs | 55 ------------ codex-rs/core/src/session/mod.rs | 31 +------ codex-rs/core/src/tasks/mod.rs | 6 -- .../src/tools/handlers/multi_agents_tests.rs | 85 +++++-------------- .../tests/suite/subagent_notifications.rs | 3 - 9 files changed, 23 insertions(+), 176 deletions(-) diff --git a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs index 4bb886a33f9c..0252c4b048ae 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs @@ -359,11 +359,6 @@ pub struct ThreadResumeParams { #[experimental("thread/resume.initialTurnsPage")] #[ts(optional = nullable)] pub initial_turns_page: Option, - /// If true, opt into emitting raw Responses API items on the event stream. - /// This is for internal use only (e.g. Codex Cloud). - #[experimental("thread/resume.experimentalRawEvents")] - #[serde(default, skip_serializing_if = "std::ops::Not::not")] - pub experimental_raw_events: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 0d1397e7989f..336f769d7155 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -1283,10 +1283,6 @@ Recoverable configuration and initialization warnings use the existing `configWa Generic runtime warnings use the `warning` notification: `{ threadId?, message }`. App-server emits this for non-fatal warnings from the core event stream, including cases where not all enabled skills are included in the model-visible skills list for a session. -### Raw response items (experimental) - -Set `experimentalRawEvents: true` on `thread/start` or `thread/resume` to receive `rawResponseItem/completed` notifications. Multi-agent v2 `agent_message` items include `metadata.source_call_id`, which matches the originating `spawn_agent`, `send_message`, or `followup_task` call id. Clients can use that field to join the raw message to the corresponding tool or activity event. - ### Notification opt-out Clients can suppress specific notifications per connection by sending exact method names in `initialize.params.capabilities.optOutNotificationMethods`. diff --git a/codex-rs/app-server/src/request_processors/thread_processor.rs b/codex-rs/app-server/src/request_processors/thread_processor.rs index fbecf10f6b0c..09ff0dd6981f 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor.rs @@ -2593,7 +2593,6 @@ impl ThreadRequestProcessor { personality, exclude_turns, initial_turns_page, - experimental_raw_events, } = params; let include_turns = !exclude_turns; @@ -2696,7 +2695,7 @@ impl ThreadRequestProcessor { self.ensure_conversation_listener( thread_id, request_id.connection_id, - experimental_raw_events, + /*raw_events_enabled*/ false, ) .await, thread_id, @@ -2970,12 +2969,6 @@ impl ThreadRequestProcessor { .thread_state_manager .thread_state(existing_thread_id) .await; - if params.experimental_raw_events { - thread_state - .lock() - .await - .set_experimental_raw_events(/*enabled*/ true); - } self.ensure_listener_task_running( existing_thread_id, existing_thread.clone(), diff --git a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs index f30f04e76edc..42711e857012 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs @@ -751,7 +751,6 @@ mod thread_processor_behavior_tests { personality: None, exclude_turns: false, initial_turns_page: None, - experimental_raw_events: false, }; let config_snapshot = ThreadConfigSnapshot { model: "gpt-5".to_string(), diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index 18bcd0c5293d..e67184bf01d3 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -3488,61 +3488,6 @@ async fn thread_resume_supports_history_and_overrides() -> Result<()> { Ok(()) } -#[tokio::test] -async fn thread_resume_can_enable_raw_events_after_restart() -> Result<()> { - let server = create_mock_responses_server_repeating_assistant("Done").await; - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path(), &server.uri())?; - - let RestartedThreadFixture { - mut mcp, thread_id, .. - } = start_materialized_thread_and_restart(codex_home.path(), "seed history").await?; - - let resume_id = mcp - .send_thread_resume_request(ThreadResumeParams { - thread_id: thread_id.clone(), - experimental_raw_events: true, - ..Default::default() - }) - .await?; - let resume_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(resume_id)), - ) - .await??; - to_response::(resume_resp)?; - - let turn_id = mcp - .send_turn_start_request(TurnStartParams { - thread_id: thread_id.clone(), - client_user_message_id: None, - input: vec![UserInput::Text { - text: "emit raw events".to_string(), - text_elements: Vec::new(), - }], - ..Default::default() - }) - .await?; - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(turn_id)), - ) - .await??; - - let notification = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("rawResponseItem/completed"), - ) - .await??; - let parsed: ServerNotification = notification.try_into()?; - let ServerNotification::RawResponseItemCompleted(notification) = parsed else { - anyhow::bail!("expected rawResponseItem/completed notification"); - }; - assert_eq!(notification.thread_id, thread_id); - - Ok(()) -} - struct RestartedThreadFixture { mcp: TestAppServer, thread_id: String, diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 2158c3085d1f..242e35f37568 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -340,7 +340,6 @@ use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::LocalImagePreparation; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; -use codex_protocol::models::ResponseItemMetadata; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::protocol::ApplyPatchApprovalRequestEvent; use codex_protocol::protocol::AskForApproval; @@ -381,24 +380,6 @@ use codex_utils_absolute_path::AbsolutePathBuf; #[cfg(test)] use codex_utils_stream_parser::ProposedPlanSegment; -struct InterAgentCompletionSourceCallId(String); - -pub(crate) fn record_inter_agent_completion_source_call_id( - turn_context: &TurnContext, - communication: &InterAgentCommunication, -) { - if communication.trigger_turn - && let Some(source_call_id) = communication - .metadata - .as_ref() - .and_then(|metadata| metadata.source_call_id.as_ref()) - { - let _ = turn_context - .extension_data - .get_or_init(|| InterAgentCompletionSourceCallId(source_call_id.clone())); - } -} - /// The high-level interface to the Codex system. /// It operates as a queue pair where you send submissions and receive events. pub struct Codex { @@ -1792,22 +1773,13 @@ impl Session { .rollout_thread_trace .is_enabled() .then(|| message.clone()); - let mut communication = InterAgentCommunication::new( + let communication = InterAgentCommunication::new( child_agent_path.clone(), parent_agent_path, Vec::new(), message, /*trigger_turn*/ false, ); - if let Some(source_call_id) = turn_context - .extension_data - .get::() - { - communication.metadata = Some(ResponseItemMetadata { - source_call_id: Some(source_call_id.0.clone()), - ..Default::default() - }); - } if let Err(err) = self .services .agent_control @@ -2701,7 +2673,6 @@ impl Session { turn_context: &TurnContext, communication: InterAgentCommunication, ) { - record_inter_agent_completion_source_call_id(turn_context, &communication); let response_item = communication.to_model_input_item(); let items = self.prepare_conversation_items_for_history( turn_context, diff --git a/codex-rs/core/src/tasks/mod.rs b/codex-rs/core/src/tasks/mod.rs index 17c2e1510583..b2f337ec42f2 100644 --- a/codex-rs/core/src/tasks/mod.rs +++ b/codex-rs/core/src/tasks/mod.rs @@ -29,7 +29,6 @@ use crate::hook_runtime::inspect_pending_input; use crate::hook_runtime::record_additional_contexts; use crate::hook_runtime::record_pending_input; use crate::session::TurnInput; -use crate::session::record_inter_agent_completion_source_call_id; use crate::session::session::Session; use crate::session::turn_context::TurnContext; use crate::state::ActiveTurn; @@ -346,11 +345,6 @@ impl Session { .clear_turn(&turn_context.sub_id); let pending_items = self.input_queue.get_pending_input(&self.active_turn).await; - for input_item in input.iter().chain(&pending_items) { - if let TurnInput::InterAgentCommunication(communication) = input_item { - record_inter_agent_completion_source_call_id(&turn_context, communication); - } - } let turn_state = { let mut active = self.active_turn.lock().await; let turn = active.get_or_insert_with(ActiveTurn::default); diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index 5ef17df50507..7b541b7cf9b0 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -2007,27 +2007,8 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() .await .expect("worker thread should exist"); let worker_path = AgentPath::try_from("/root/worker").expect("worker path"); - let spawn_communication = manager - .captured_ops() - .into_iter() - .find_map(|(id, op)| { - (id == agent_id).then_some(op).and_then(|op| match op { - Op::InterAgentCommunication { communication } - if communication.encrypted_content.as_deref() == Some("boot worker") => - { - Some(communication) - } - _ => None, - }) - }) - .expect("spawn communication should be captured"); let first_turn = thread.codex.session.new_default_turn().await; - thread - .codex - .session - .record_inter_agent_communication(first_turn.as_ref(), spawn_communication) - .await; thread .codex .session @@ -2043,26 +2024,23 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() ) .await; - let mut followup_invocation = invocation( - session, - turn, - "followup_task", - function_payload(json!({ - "target": agent_id.to_string(), - "message": "continue", - })), - ); - followup_invocation.call_id = "followup-call".to_string(); FollowupTaskHandlerV2 - .handle(followup_invocation) + .handle(invocation( + session, + turn, + "followup_task", + function_payload(json!({ + "target": agent_id.to_string(), + "message": "continue", + })), + )) .await .expect("followup_task should succeed"); - let followup_communication = manager - .captured_ops() - .into_iter() - .find_map(|(id, op)| { - (id == agent_id).then_some(op).and_then(|op| match op { + assert!(manager.captured_ops().iter().any(|(id, op)| { + *id == agent_id + && matches!( + op, Op::InterAgentCommunication { communication } if communication.author == AgentPath::root() && communication.recipient == worker_path @@ -2071,22 +2049,12 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() .metadata .as_ref() .and_then(|metadata| metadata.source_call_id.as_deref()) - == Some("followup-call") - && communication.trigger_turn => - { - Some(communication) - } - _ => None, - }) - }) - .expect("followup communication should be captured"); + == Some("call-1") + && communication.trigger_turn + ) + })); let second_turn = thread.codex.session.new_default_turn().await; - thread - .codex - .session - .record_inter_agent_communication(second_turn.as_ref(), followup_communication) - .await; thread .codex .session @@ -2130,12 +2098,7 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() && communication.other_recipients.is_empty() && !communication.trigger_turn => { - Some(( - communication.content, - communication - .metadata - .and_then(|metadata| metadata.source_call_id), - )) + Some(communication.content) } _ => None, }) @@ -2143,11 +2106,11 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() .collect::>(); let first_count = notifications .iter() - .filter(|(message, _)| *message == first_notification) + .filter(|message| **message == first_notification) .count(); let second_count = notifications .iter() - .filter(|(message, _)| *message == second_notification) + .filter(|message| **message == second_notification) .count(); if first_count == 1 && second_count == 1 { break notifications; @@ -2158,13 +2121,7 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() .await .expect("parent should receive one completion notification per child turn"); - assert_eq!( - notifications, - vec![ - (first_notification, Some("call-1".to_string())), - (second_notification, Some("followup-call".to_string())), - ] - ); + assert_eq!(notifications.len(), 2); } #[tokio::test] diff --git a/codex-rs/core/tests/suite/subagent_notifications.rs b/codex-rs/core/tests/suite/subagent_notifications.rs index d017ad477289..2501aa0dde31 100644 --- a/codex-rs/core/tests/suite/subagent_notifications.rs +++ b/codex-rs/core/tests/suite/subagent_notifications.rs @@ -1234,9 +1234,6 @@ async fn plaintext_multi_agent_v2_completion_sends_agent_message( "type": "agent_message", "author": "/root/worker", "recipient": "/root", - "metadata": { - "source_call_id": SPAWN_CALL_ID, - }, "content": [{ "type": "input_text", "text": notification,