Repository navigation
Regression: encrypted MultiAgentV2 messages remove readable task audit trail #28058
Description
What version of Codex CLI is running?
Upstream main after #26210 (Encrypt multi-agent v2 message payloads, merged 2026-06-05). This appears to affect versions that include that change and enable MultiAgentV2 (post-0.137.0).
What subscription do you have?
Not subscription-specific.
Which model were you using?
Not model-specific. This concerns MultiAgentV2 spawn_agent, send_message, and followup_task message handling.
What platform is your computer?
Not platform-specific.
What terminal emulator and version are you using (if applicable)?
Not terminal-specific.
Codex doctor report
Not applicable. The regression is visible from the merged code behavior in #26210 rather than from local environment state.
What issue are you seeing?
#26210 makes MultiAgentV2 agent task/message payloads opaque to Codex by marking the model-facing message parameter as encrypted, storing only InterAgentCommunication.encrypted_content, and leaving InterAgentCommunication.content empty.
The encrypted delivery path is understandable as privacy hardening, but it also removes the human-readable task/message text from local rollout history, trace reduction, and parent-side audit/debug surfaces. That makes it difficult to answer basic questions such as:
- What task did this
spawn_agentcall give the child agent? - What message was sent to a subagent?
- Why did a child thread exist when reviewing a rollout after the fact?
This is different from #26753, which reports request validation failures for encrypted tool schemas. This issue is about auditability and debuggability after the encrypted schema is accepted.
What steps can reproduce the bug?
- Use a build containing Encrypt multi-agent v2 message payloads #26210 with MultiAgentV2 enabled. (aka post-0.137.0)
- Have the model call
spawn_agent,send_message, orfollowup_task. - Inspect the parent rollout/history/trace for the subagent task.
- The task/message content is hidden behind ciphertext rather than being available as human-readable audit text.
What is the expected behavior?
Codex should preserve a human-readable, structured audit copy of the subagent task/message while still allowing encrypted delivery to the recipient model.
A possible shape is to keep the encrypted message field for model delivery, but add a separate non-encrypted audit field for the readable task text. The audit field should be persisted in rollout/history/trace metadata so users and maintainers can inspect what was delegated without needing to decrypt model-delivery ciphertext.
Additional information
Related PR/issues:
- Encryption change: Encrypt multi-agent v2 message payloads #26210
- Related but distinct schema-validation issue: MultiAgentV2 encrypted spawn_agent schema returns 400: model not configured for encrypted tool use #26753
The goal is not necessarily to revert encrypted delivery. The concern is that encrypted delivery should not fully remove local human auditability for subagent delegation.
Current status (2026-07-22)
This remains unresolved on current upstream main:
spawn_agent,send_message, andfollowup_taskstill expose only an encryptedmessagefield.- All three handlers still construct
InterAgentCommunication::new_encrypted(), leaving readablecontentempty. - The structured communication log still substitutes
encrypted_contentinto itscontentfield when plaintext is absent. - Rollout-trace still uses encrypted delivery content as
message_content. list_agentsno longer exposes the most recent task/message.
A complete fork implementation now covers all three tools and makes delivery policy explicit:
[features.multi_agent_v2]
message_delivery = "plaintext" # encrypted | encrypted_with_audit | plaintextencryptedpreserves the current upstream opaque behavior.encrypted_with_auditkeeps encrypted recipient delivery and requires a separate readabletask_message.plaintextemits and persists one readable message, avoiding duplicate model output.
The implementation:
- validates the selected schema/arguments before agent lookup or spawn;
- applies one combined 8 KiB hard limit to each communication payload;
- keeps the audit copy out of encrypted recipient-model input;
- persists readable content in parent tool arguments and
InterAgentCommunication.content; - prevents ciphertext from being presented as readable communication-log or rollout-trace content;
- restores
last_task_messagefor livelist_agentsinspection and serializes mailbox delivery with metadata updates; - preserves encrypted/plaintext communication representation through rollout resume and fork handling;
- carries readable V2 spawn/follow-up text through
SubAgentActivityinto the parent TUI instead of showing onlyStarted /root/child; - projects child-side
ResponseItem::AgentMessagerecords into typed app-server/TUI history with sender attribution, stable IDs, encrypted placeholders, resume/pagination support, and duplicate suppression; - covers
spawn_agent,send_message, andfollowup_task, including encrypted-only, encrypted-with-audit, and plaintext modes.
Current implementation commits:
- Configurable delivery policy:
ignatremizov@2ffe360 - All three V2 communication tools, audit persistence, and live inspection:
ignatremizov@3b523d7 - Rollout-trace audit retention and encrypted-delivery correlation:
ignatremizov@ebb53b6 - Configurable multi-line task and response previews:
ignatremizov@7fd1514 - Parent-side V2 task propagation and child-history projection:
ignatremizov@f6ddcea - Durable live/resumed inter-agent transcript delivery:
ignatremizov@b60e626
Source analysis
Upstream InterAgentCommunication::new_encrypted() deliberately initializes content as an empty string and stores the payload only in encrypted_content:
codex/codex-rs/protocol/src/protocol.rs
Lines 735 to 791 in bdd3118
| #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, TS)] | |
| pub struct InterAgentCommunication { | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| #[ts(optional)] | |
| pub id: Option<ResponseItemId>, | |
| pub author: AgentPath, | |
| pub recipient: AgentPath, | |
| #[serde(default)] | |
| pub other_recipients: Vec<AgentPath>, | |
| pub content: String, | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| #[ts(optional)] | |
| pub encrypted_content: Option<String>, | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| #[ts(optional)] | |
| pub internal_chat_message_metadata_passthrough: Option<InternalChatMessageMetadataPassthrough>, | |
| pub trigger_turn: bool, | |
| } | |
| impl InterAgentCommunication { | |
| pub fn new( | |
| author: AgentPath, | |
| recipient: AgentPath, | |
| other_recipients: Vec<AgentPath>, | |
| content: String, | |
| trigger_turn: bool, | |
| ) -> Self { | |
| Self { | |
| id: None, | |
| author, | |
| recipient, | |
| other_recipients, | |
| content, | |
| encrypted_content: None, | |
| internal_chat_message_metadata_passthrough: None, | |
| trigger_turn, | |
| } | |
| } | |
| pub fn new_encrypted( | |
| author: AgentPath, | |
| recipient: AgentPath, | |
| other_recipients: Vec<AgentPath>, | |
| encrypted_content: String, | |
| trigger_turn: bool, | |
| ) -> Self { | |
| Self { | |
| id: None, | |
| author, | |
| recipient, | |
| other_recipients, | |
| content: String::new(), | |
| encrypted_content: Some(encrypted_content), | |
| internal_chat_message_metadata_passthrough: None, | |
| trigger_turn, | |
| } |
The conversion used for recipient history then emits only the encrypted payload whenever encrypted_content is present. Merely populating the runtime content field would therefore not create a readable persisted ResponseItem; the fix also needs an explicit local audit persistence path:
codex/codex-rs/protocol/src/protocol.rs
Lines 814 to 846 in bdd3118
| pub fn to_model_input_item(&self) -> ResponseItem { | |
| let content = match &self.encrypted_content { | |
| Some(encrypted_content) => { | |
| let message_type = if self.trigger_turn { | |
| "NEW_TASK" | |
| } else { | |
| "MESSAGE" | |
| }; | |
| vec![ | |
| AgentMessageInputContent::InputText { | |
| text: format!( | |
| "Message Type: {message_type}\nTask name: {}\nSender: {}\nPayload:\n", | |
| self.recipient, self.author | |
| ), | |
| }, | |
| AgentMessageInputContent::EncryptedContent { | |
| encrypted_content: encrypted_content.clone(), | |
| }, | |
| ] | |
| } | |
| None => vec![AgentMessageInputContent::InputText { | |
| text: self.content.clone(), | |
| }], | |
| }; | |
| ResponseItem::AgentMessage { | |
| id: self.id.clone(), | |
| author: self.author.to_string(), | |
| recipient: self.recipient.to_string(), | |
| content, | |
| internal_chat_message_metadata_passthrough: self | |
| .internal_chat_message_metadata_passthrough | |
| .clone(), | |
| } |
The current v2 message helper constructs encrypted communication with empty plaintext content:
codex/codex-rs/core/src/tools/handlers/multi_agents_v2.rs
Lines 54 to 65 in bdd3118
| pub(super) fn communication_from_tool_message( | |
| author: AgentPath, | |
| recipient: AgentPath, | |
| message: String, | |
| ) -> InterAgentCommunication { | |
| InterAgentCommunication::new_encrypted( | |
| author, | |
| recipient, | |
| Vec::new(), | |
| message, | |
| /*trigger_turn*/ true, | |
| ) |
send_message and followup_task still deserialize only target plus the encrypted message, then pass that ciphertext directly through the shared helper. There is no plaintext companion available to persist:
| #[derive(Debug, Deserialize)] | |
| #[serde(deny_unknown_fields)] | |
| /// Input for the MultiAgentV2 `send_message` tool. | |
| pub(crate) struct SendMessageArgs { | |
| pub(crate) target: String, | |
| pub(crate) message: String, | |
| } | |
| #[derive(Debug, Deserialize)] | |
| #[serde(deny_unknown_fields)] | |
| /// Input for the MultiAgentV2 `followup_task` tool. | |
| pub(crate) struct FollowupTaskArgs { | |
| pub(crate) target: String, | |
| pub(crate) message: String, | |
| } | |
| pub(super) fn message_content(message: String) -> Result<String, FunctionCallError> { | |
| if message.trim().is_empty() { | |
| return Err(FunctionCallError::RespondToModel( | |
| "Empty message can't be sent to an agent".to_string(), | |
| )); | |
| } | |
| Ok(message) | |
| } | |
| /// Handles the shared MultiAgentV2 message flow for both `send_message` and `followup_task`. | |
| pub(crate) async fn handle_message_string_tool( | |
| invocation: ToolInvocation, | |
| mode: MessageDeliveryMode, | |
| target: String, | |
| message: String, | |
| ) -> Result<FunctionToolOutput, FunctionCallError> { | |
| let message = message_content(message)?; |
codex/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs
Lines 99 to 114 in bdd3118
| let author = turn | |
| .session_source | |
| .get_agent_path() | |
| .unwrap_or_else(AgentPath::root); | |
| let communication = | |
| communication_from_tool_message(author, receiver_agent_path.clone(), message); | |
| let kind = match mode { | |
| MessageDeliveryMode::QueueOnly => AgentCommunicationKind::Message, | |
| MessageDeliveryMode::TriggerTurn => AgentCommunicationKind::Followup, | |
| }; | |
| let context = AgentCommunicationContext::new(kind, session.thread_id); | |
| let result = session | |
| .services | |
| .agent_control | |
| .send_inter_agent_communication(receiver_thread_id, mode.apply(communication), context) | |
| .await |
The receiver records the model-facing ResponseItem produced by to_model_input_item(). For encrypted communication that item contains the encrypted delivery payload, not readable audit text:
codex/codex-rs/core/src/session/mod.rs
Lines 2957 to 2978 in bdd3118
| pub(crate) async fn record_inter_agent_communication( | |
| &self, | |
| turn_context: &TurnContext, | |
| mut communication: InterAgentCommunication, | |
| ) { | |
| communication.set_turn_id_if_missing(&turn_context.sub_id); | |
| let response_item = communication.to_model_input_item(); | |
| let items = self.prepare_conversation_items_for_history( | |
| turn_context, | |
| std::slice::from_ref(&response_item), | |
| ); | |
| let items = items.as_ref(); | |
| let response_item = items[0].clone(); | |
| { | |
| let mut state = self.state.lock().await; | |
| state.current_time_reminder.note_recorded_items(items); | |
| state.record_items( | |
| items.iter(), | |
| turn_context.model_info.truncation_policy.into(), | |
| ); | |
| } | |
| self.persist_rollout_items(&[ |
The structured communication log has the same fallback: when content is empty, it records encrypted_content as the event content:
codex/codex-rs/core/src/agent_communication.rs
Lines 44 to 66 in bdd3118
| pub(crate) fn emit_agent_communication_send( | |
| communication_id: &str, | |
| context: &AgentCommunicationContext, | |
| communication: &InterAgentCommunication, | |
| receiver_thread_id: ThreadId, | |
| ) { | |
| tracing::info!( | |
| target: AGENT_COMMUNICATION_TARGET, | |
| { | |
| event.name = "codex.agent_communication", | |
| communication_id, | |
| kind = context.kind.as_str(), | |
| state = "send", | |
| sender_thread_id = %context.sender_thread_id, | |
| receiver_thread_id = %receiver_thread_id, | |
| content = if communication.content.is_empty() { | |
| communication.encrypted_content.as_deref().unwrap_or_default() | |
| } else { | |
| communication.content.as_str() | |
| }, | |
| }, | |
| "agent communication" | |
| ); |
Why changing the V2 tool schema is necessary but not sufficient for the TUI
The tool-schema/handler change makes readable text exist locally again, but current upstream main still drops that text before the relevant parent and child transcript surfaces.
For the parent view, upstream's SubAgentActivityEvent carries only the event ID, child ID/path, and activity kind. It has no task/prompt field:
codex/codex-rs/protocol/src/protocol.rs
Lines 4340 to 4350 in bdd3118
| #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)] | |
| pub struct SubAgentActivityEvent { | |
| pub event_id: String, | |
| #[serde(default)] | |
| pub occurred_at_ms: i64, | |
| /// Thread ID of the affected sub-agent. | |
| pub agent_thread_id: ThreadId, | |
| /// Canonical v2 path of the affected sub-agent. | |
| pub agent_path: AgentPath, | |
| pub kind: SubAgentActivityKind, | |
| } |
The app-server ThreadItem::SubAgentActivity likewise exposes no prompt:
codex/codex-rs/app-server-protocol/src/protocol/v2/item.rs
Lines 357 to 364 in bdd3118
| #[serde(rename_all = "camelCase")] | |
| #[ts(rename_all = "camelCase")] | |
| SubAgentActivity { | |
| id: String, | |
| kind: SubAgentActivityKind, | |
| agent_thread_id: String, | |
| agent_path: String, | |
| }, |
The upstream TUI therefore renders only Started, Interacted with, or Interrupted plus the agent path, with an empty detail list:
codex/codex-rs/tui/src/multi_agents.rs
Lines 303 to 320 in bdd3118
| pub(crate) fn sub_agent_activity_history_cell(item: &ThreadItem) -> Option<PlainHistoryCell> { | |
| let ThreadItem::SubAgentActivity { | |
| kind, agent_path, .. | |
| } = item | |
| else { | |
| return None; | |
| }; | |
| Some(collab_event( | |
| sub_agent_activity_title(*kind, agent_path), | |
| Vec::new(), | |
| )) | |
| } | |
| pub(crate) fn sub_agent_activity_summary(kind: SubAgentActivityKind, agent_path: &str) -> String { | |
| match kind { | |
| SubAgentActivityKind::Started => format!("Started `{agent_path}`"), | |
| SubAgentActivityKind::Interacted => format!("Interacted with `{agent_path}`"), | |
| SubAgentActivityKind::Interrupted => format!("Interrupted `{agent_path}`"), |
For the child view, readable plaintext can already be present canonically as ResponseItem::AgentMessage, but upstream thread-history reconstruction ignores InterAgentCommunication records and its generic response-item handler accepts only hook-prompt user messages:
codex/codex-rs/app-server-protocol/src/protocol/thread_history.rs
Lines 395 to 407 in bdd3118
| pub fn handle_rollout_item(&mut self, item: &RolloutItem) { | |
| self.current_rollout_index = self.next_rollout_index; | |
| self.next_rollout_index += 1; | |
| match item { | |
| RolloutItem::EventMsg(event) => self.handle_event(event), | |
| RolloutItem::Compacted(payload) => self.handle_compacted(payload), | |
| RolloutItem::ResponseItem(item) => self.handle_response_item(item), | |
| RolloutItem::InterAgentCommunication(_) | |
| | RolloutItem::InterAgentCommunicationMetadata { .. } | |
| | RolloutItem::TurnContext(_) | |
| | RolloutItem::WorldState(_) | |
| | RolloutItem::SessionMeta(_) => {} | |
| } |
codex/codex-rs/app-server-protocol/src/protocol/thread_history.rs
Lines 446 to 455 in bdd3118
| fn handle_response_item(&mut self, item: &codex_protocol::models::ResponseItem) { | |
| let codex_protocol::models::ResponseItem::Message { | |
| role, content, id, .. | |
| } = item | |
| else { | |
| return; | |
| }; | |
| if role != "user" { | |
| return; |
Live raw response items are emitted only through the separate rawResponseItem/* notification path; that is not the normal typed thread-item stream consumed by the TUI and app-server clients:
codex/codex-rs/app-server/src/bespoke_event_handling.rs
Lines 1045 to 1053 in bdd3118
| EventMsg::RawResponseItem(raw_response_item_event) => { | |
| maybe_emit_raw_response_item_completed( | |
| conversation_id, | |
| &event_turn_id, | |
| raw_response_item_event.item, | |
| &outgoing, | |
| ) | |
| .await; | |
| } |
codex/codex-rs/app-server/src/bespoke_event_handling.rs
Lines 1418 to 1431 in bdd3118
| async fn maybe_emit_raw_response_item_completed( | |
| conversation_id: ThreadId, | |
| turn_id: &str, | |
| item: codex_protocol::models::ResponseItem, | |
| outgoing: &ThreadScopedOutgoingMessageSender, | |
| ) { | |
| let notification = RawResponseItemCompletedNotification { | |
| thread_id: conversation_id.to_string(), | |
| turn_id: turn_id.to_string(), | |
| item, | |
| }; | |
| outgoing | |
| .send_server_notification(ServerNotification::RawResponseItemCompleted(notification)) | |
| .await; |
So a complete local fix has two layers:
- Make V2 communication readable under an explicit delivery policy and persist that representation.
- Carry readable parent activity prompts and child
AgentMessagerecords through protocol conversion, app-server history/live notifications, resume/pagination, and TUI rendering.
The fork implements the second layer in the final three commits linked above. Plaintext messages render with sender attribution; encrypted-only messages render a fixed Input message encrypted placeholder rather than ciphertext. The durable projection is fork-owned and rebuildable from canonical plain or compressed rollouts so the same SQLite cache remains compatible with upstream Codex.
Implementation / fix spec
A concrete implementation can preserve encrypted delivery and restore a local audit trail:
- Keep the existing encrypted
messagefield as the delivery payload. - Add a required, non-encrypted plaintext companion to each v2 communication tool:
spawn_agent:task_messagesend_messageandfollowup_task: a consistently named plaintext audit field, such astask_messageormessage_text
- Reject empty plaintext audit values at the handler boundary.
- Construct
InterAgentCommunicationwith both:encrypted_contentset to the encryptedmessagecontentset to the plaintext audit copy
- Keep
to_model_input_item()behavior unchanged so the recipient model still receives ciphertext, not the local audit copy. - Persist the plaintext companion in the parent tool invocation/rollout and retain it in structured trace edges and local communication logs.
- Match tool calls to delivered child items using ciphertext/IDs, not plaintext equality. The plaintext field is audit metadata and should not replace the encrypted delivery identity.
- Bound the plaintext audit field with the same hard size limit as the corresponding delegated message so the new rollout/context item cannot grow without limit.
- Add readable prompt content to V2 sub-agent activity items and render it with a bounded TUI preview.
- Convert persisted and live
ResponseItem::AgentMessagerecords into normal typed thread items with sender attribution, stable IDs, and explicit encrypted-content redaction. - Preserve ordering and suppress duplicates across thread read/resume, pagination, running-thread subscription handoff, and agent switching.
The fork implementation linked in the current-status section now applies this contract consistently to spawn_agent, send_message, and followup_task, including local inspection, communication logs, and rollout-trace reduction.
Acceptance criteria
- Parent rollout/history shows the readable text for v2
spawn_agent,send_message, andfollowup_task. - Parent TUI activity rows show the readable task/follow-up text rather than only the child path.
- Child app-server/TUI history shows the same readable instruction with sender attribution; encrypted-only delivery shows a non-sensitive placeholder, never ciphertext.
- The child model still receives only the encrypted delivery payload when encryption is enabled.
- Structured rollout-trace interaction edges carry bounded plaintext
message_content. - Communication logs use plaintext audit content when present and never substitute ciphertext into a field presented as readable message text.
- Live notifications,
thread/read, resume, pagination, and agent switching preserve one ordered copy of each communication. - Resume/replay preserves the audit copy without injecting it into the child model context, and remains compatible with canonical plain and compressed rollouts.
- Existing plaintext v1 communication behavior is unchanged.
- Regression tests cover all three v2 tools and assert both sides of the contract: readable local audit data and encrypted recipient-model input, plus parent/child TUI rendering and live/resumed app-server history.
Activity
ignatremizov commentedon Jun 13, 2026
Guys, we don’t want to build Skynet and then be unable to audit what it’s doing.
septrcode commentedon Jun 14, 2026
I too would like to be able to audit my agents… if we can’t audit agents… only providers can… it’s clear we are on a path to enterprise only has access to these tools, why restrict otherwise
Alek2077 commentedon Jul 4, 2026
The auditability concern is no longer meaningfully opt-in for GPT-5.5 users on Codex 0.142.5.
[now applies to Sol and Terra, while 5.5 has been reverted to using v1 again]
The current GPT-5.5 model catalog sets
multi_agent_version: v2, and the 0.142.5 runtime gives that value precedence overfeatures.multi_agent_v2=false. In a fresh GPT-5.5 thread,features listsays V2 is disabled, butspawn_agentstill exposes the V2 task-path schema. A fresh GPT-5.4 thread with the same configuration uses V1 normally.Because GPT-5.5 forces the encrypted V2 path, users selecting the current frontier model cannot choose the readable V1 delegation trail through the documented feature setting. That turns the loss of locally readable delegated-task content from an experimental opt-in tradeoff into mandatory behavior for that model.
The same forced surface also prevents selection of documented custom-agent profiles because it hides the agent type, model, and reasoning controls. This compounds the auditability problem: GPT-5.5 users cannot select the configured reviewer profile and also cannot inspect the plaintext task that a generic replacement received.
I have filed the configuration-precedence reproduction separately: #31097
ignatremizov commentedon Jul 7, 2026
Upd: #30867, and #30872 by @bolinfest and @rka-oai are directionally related, but they don’t address this issue yet.
The PRs add/prepare structured lifecycle logging for multi-agent communication, which is useful for operational observability. The gap here is specifically local auditability of the actual task/message text in rollout/history/replay/debug surfaces.
For encrypted MultiAgentV2 messages, the new logging path still falls back to
encrypted_contentwhen plaintextcontentis empty, so it will record an opaque encrypted payload rather than the human-readable task the model delegated.A reminder that the desired behavior here is narrow: keep encrypted delivery if needed for some reason, and also persist a human-readable structured audit copy of
spawn_agent/send_message/followup_tasktask text in the parent rollout history so users can later answer "what did this agent get asked to do?" without relying on provider-side visibility or opt-in trace logging.runfence commentedon Jul 14, 2026
Please revert this! I need to be able to inspect those messages to debug my multi-agent workflows.
Who thought this is a good idea???
embedding-shapes commentedon Jul 14, 2026
I'm also being hit by this, playing around with GPT 5.6 Sol Ultra (which spawns sub-agents) and there is no way to introspect exactly what the main model sent the sub-agents, what on earth?! We have to be able to introspect exactly what inputs inference use, otherwise how are we supposed to be able to understand what went wrong, when things go wrong?
This seems like a completely bananas thing to implement, encrypt local content not to protect end-users, but to protect OpenAI from other providers extracting how the multi-agent stuff works? This basically makes the whole flashy new "Ultra" stuff unusable, as we have 0 introspection into what instructions they actually received when they started.
I came across the encrypted stuff some weeks or months ago even, thought it was just experimental things or things for enterprise users who sometimes want encrypted-at-rest stuff. Imagine my surprise when I now tried to integrate with the new stuff via app-server and y'all are trying to hide the actual prompts from us paying users?!
Please carefully consider resolving this as otherwise it becomes really hard to justify continuing to use codex as you're moving towards hiding stuff from us users rather than making it easier to work with codex.
Aquaticat commentedon Jul 14, 2026
My current workaround: Not using subagents at all.
ZenulAbidin commentedon Jul 14, 2026
Sorry to shamelessly plug my project in, but I just created stringbean to solve this problem.
You can use it to run your own agents in Codex, even non-OpenAI models.
cooluser69 commentedon Jul 14, 2026
This is bad!
runfence commentedon Jul 14, 2026
Moreover it doesn't really protect you from competition.
ignatremizov commentedon Jul 15, 2026
Update: #33030 removed
last_task_messagefromlist_agentsand stopped tracking the most recent instruction in agent metadata. This makes the auditability problem described here even more significant: users can see that an agent exists and what status it has, but not what it was asked to do anymore at all.Switching to the child thread does not fully replace this information because as mentioned, encrypted v2 task delivery is stored as opaque
encrypted_content; the original readable delegation is not available in the local rollout for agent switching.The #33030 description explains what was removed, but not why. Was this motivated by privacy, context size, correctness, or another constraint? Some attempt at building a moat? Anti-distillation? It's strange.
Agent inspection is operationally important for the average user. It should remain possible to answer:
One possible design is a configurable delivery policy:
This would preserve encrypted behavior while allowing users who prioritize local auditability or token efficiency to choose the appropriate tradeoff. At minimum, I think a readable task description should remain available somewhere in history, trace metadata, and/or
list_agents.feat(anatomy,ai-agents): add agent-run simulator slides with vendor t…
15 remaining items
feat(multi-agent): add plaintext v2 message delivery
34262315716 commentedon Jul 24, 2026
Strongly agree with this issue. The encrypted MultiAgentV2 messages effectively lock users into OpenAI's proprietary model ecosystem on an allegedly open-source project. If the goal is to push users toward paying for OpenAI models, why not just be upfront about it and make it a documented Codex Pro feature? At least that would be honest. But hiding vendor lock-in behind 'encryption for security' in an open-source project is a terrible look. This is not what open source spirit is about.
ignatremizov commentedon Jul 29, 2026
Update: Merged PR #35845 is a substantial partial fix and confirms that plaintext MultiAgent V2 delivery is supported by the local Codex orchestration path.
When Responses returns a collaboration function call with
encrypted_function_args: [], Codex now classifies it as a plaintext collaboration call.spawn_agent,send_message, andfollowup_taskthen construct a plaintextInterAgentCommunication, and the child receives a structured plaintextAgentMessagecontaining the message type, task name, sender, and payload. The marker and readable function arguments are also preserved across request replay.Relevant implementation:
codex/codex-rs/core/src/tools/router.rs
Lines 34 to 67 in 03edf16
codex/codex-rs/core/src/tools/handlers/multi_agents_v2.rs
Lines 57 to 84 in 03edf16
codex/codex-rs/core/src/context/inter_agent_message.rs
Lines 44 to 66 in 03edf16
This resolves the most important underlying point: the parent-generated task can now remain plaintext through the local parent-to-child handoff. It is not yet a complete user-visible audit trail, however:
[plaintext arguments]/[plaintext].SubAgentActivitystill carries no prompt, so the parent TUI still renders onlyStarted /root/childwith an empty detail list.InterAgentCommunicationand does not project persistedResponseItem::AgentMessagerecords into typed thread history.Current upstream TUI/history paths:
codex/codex-rs/tui/src/multi_agents.rs
Lines 303 to 313 in fe01054
codex/codex-rs/app-server-protocol/src/protocol/thread_history.rs
Lines 400 to 409 in fe01054
So I would consider #35845 a substantial partial resolution of this issue. The remaining work is to carry the now-readable task through
SubAgentActivity, typed app-server live/history/resume paths, and parent/child TUI rendering, with sender attribution and encrypted-content redaction.92645417d9e5c763259dbebc306e3e commentedon Jul 29, 2026
These modifications primarily allow third-party models to use the multi-agent v2 tool; the type of instruction returned is always determined by the server side.
wuyak commentedon Aug 9, 2026
A current Codex App reproduction shows an output-side consequence of this issue, not just missing delegated input.
send_message.Evidence capsule delivered to /root: ....AgentMessageand the separate shortFINAL_ANSWER.The expanded inspector should show an ordered operational transcript: initial assignment, parent-to-child follow-ups, child-to-parent messages/evidence capsules, and the terminal result, with sender, recipient, timestamp, operation type, and delivery state. A collapsed row could summarize something like
Completed · 57s · Handoff sent · 2 blockers · 10 findings.This does not require chain-of-thought exposure. It is about user-visible messages that agents explicitly send to each other. The same representation should survive live view, history, resume, and replay. Users should not have to duplicate a full capsule into the terminal result solely to make it visible in the App.
Related UI reports: #32125 covers missing orchestrator/subagent communications, and #36266 proposes a richer expandable Subagents panel.
fwf-ben commentedon Aug 18, 2026
Just got bitten by this: can't run evals against the subagent rollouts, without their prompt can't re-run them or improve them. Why was this done in the first place? Seems a crazy regression.
arthurlapertosa commentedon Aug 28, 2026
This is ridiculous, we have to be able to view and inspect our subagents too. I mean, it's part of the machine that will build your solution, so you gotta be able to view it. That's so crazy that they encrypted it.
embedding-shapes commentedon Aug 28, 2026
Yeah, I'm not sure what they were thinking.
I want to use
ultra, but since it starts spawning sub-agents, and I can't inspect their prompts, I literally don't dare to use it. So I'm stuck atmax, because I want to be able to see what the agents are doing.They're literally hiding stuff we need for debugging, and leaving money on the table while doing so. Really strange decision overall.
VolkumDev commentedon Aug 30, 2026
OpenAI team will stop being so incompetent some day?? Holy fuck
jiahao-d commentedon Sep 1, 2026
Also reproduced on Codex desktop for Windows with bundled codex-cli 0.151.0-alpha.7.2. I used subagents for an independent evaluation and needed to confirm that two runs received exactly the same task, without leading language or a leaked expected answer. Codex stored only ciphertext in both the parent and child histories, so I could not compare the delegated tasks or determine whether different results came from model variance or different instructions. This makes the evaluation impossible to audit or reproduce.
future3OOO commentedon Sep 14, 2026
Additional observation on Codex CLI 0.154.0, Linux/WSL2, OpenAI provider,
gpt-5.6-sol, Multi-Agent V2 (2026-09-14 UTC).During a real repository task, the parent spawned three explorer agents with
fork_turns="all". We needed to verify whether the parent supplied the required task contract, scoped context summary, target files and GitNexus repository/status, because two delegates repeated a graph query and one printed a large truncated response.Read-only inspection of the parent rollout, all three child rollouts, native log database and thread-history projection found:
spawn_agent.messagevalues are ciphertext.NEW_TASKcontains a readable sender/recipient header followed byencrypted_content.InterAgentCommunicationlogs show empty plaintextcontentand populatedencrypted_content.subAgentActivityhistory entries contain lifecycle/identity metadata, without the assignment text. No readable assignment was recovered from these inspected surfaces.Consequently, we could confirm their actions but could not establish whether the exact dispatch omitted constraints or whether the delegates disregarded adequate instructions. A model-authored retrospective summary would not resolve that distinction.
Please provide or identify a supported, authorized way to inspect the exact dispatched assignments and follow-ups, correlated to sender/recipient and execution. For a fix, compare real initial/follow-up dispatch on unchanged and candidate runtimes: the exact input must become auditable while recipient behavior and existing permission boundaries remain preserved. No new workflow stages or duplicate model-authored message logs are needed.
This corroborates this issue; it does not establish compromise, unauthorized background activity, or malicious intent. No credentials, ciphertext payloads, or private task contents are included here.
pedroyan commentedon Sep 17, 2026
+1 on this issue. If I cannot inspect the prompt that the parent agent is passing to its sub-agents, I can't be certain that the correct task is being executed.
For all I know, they could be secretly cooking Skynet on my machine without me even knowing
nleononline commentedon Sep 20, 2026
Two and a half months later, this still hasn't been fixed...
xlionjuan commentedon Oct 13, 2026
Still NOTHING
embedding-shapes commentedon Oct 13, 2026
@tibo-openai how on earth are we supposed to be able to use "ultra" or sub-agents even, when we're not able to troubleshoot when things invariably go wrong? It's impossible to figure out why a sub-agent messed up because we're unable to see the prompts the "root" session sent, so what really is the point of even having sub-agents or "ultra", when any serious programmer would be unable to use it?
You have this "super" feature that consumes a ton more usage, yet you're not caring enough about the feature to actually allow us to us it, what's the point?