Consolidate multi-agent v2 communication sends (#30867)

## Why

Multi-agent v2 communications currently use separate outbound paths:
direct messages, follow-up tasks, and completion results go through
`send_inter_agent_communication`, while a spawn's initial message goes
through the generic input submission path. That split makes it difficult
to add complete communication lifecycle logging in one place.

This refactor makes `submit_inter_agent_communication` the common sink
for those paths, preparing the follow-up observability work discussed in
[#30516](https://github.com/openai/codex/pull/30516).

## What changed

- Routed all current outbound `InterAgentCommunication` paths in
`AgentControl`—direct messages, follow-up tasks, completion results, and
multi-agent v2 spawn initial messages—through
`submit_inter_agent_communication`.
- Centralized the actual submission and last-task-message bookkeeping
there, providing one place for the follow-up PR to instrument
communication creation and successful enqueue.
- Left non-communication input handling and the multi-agent v1 spawn
flow unchanged.

## Testing

- `just test -p codex-core 'agent::control::tests::'` (51 passed)
- `just test -p codex-core
'suite::subagent_notifications::encrypted_multi_agent_v2_spawn_sends_agent_message_to_child'`
(passed)








---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/30867).
* #30872
* __->__ #30867
This commit is contained in:
Michael Bolin
2026-07-01 17:08:11 -07:00
committed by GitHub
parent 042e61726d
commit a98a21798c
+24 -10
View File
@@ -154,12 +154,13 @@ impl AgentControl {
state: &Arc<ThreadManagerState>,
initial_operation: Op,
) -> CodexResult<String> {
let last_task_message = match &initial_operation {
Op::InterAgentCommunication { communication } => {
last_task_message_from_communication(communication)
}
_ => non_empty_task_message(render_input_preview(&initial_operation)),
};
if let Op::InterAgentCommunication { communication } = initial_operation {
return self
.submit_inter_agent_communication(agent_id, state, communication)
.await;
}
let last_task_message = non_empty_task_message(render_input_preview(&initial_operation));
let result = self
.handle_thread_request_result(
agent_id,
@@ -182,13 +183,26 @@ impl AgentControl {
&self,
agent_id: ThreadId,
communication: InterAgentCommunication,
) -> CodexResult<String> {
self.send_input(agent_id, Op::InterAgentCommunication { communication })
.await
}
async fn submit_inter_agent_communication(
&self,
agent_id: ThreadId,
state: &Arc<ThreadManagerState>,
communication: InterAgentCommunication,
) -> CodexResult<String> {
let last_task_message = last_task_message_from_communication(&communication);
let state = self.upgrade()?;
let op = Op::InterAgentCommunication { communication };
self.ensure_execution_capacity_for_op(agent_id, &op).await?;
let result = self
.handle_thread_request_result(agent_id, &state, state.send_op(agent_id, op).await)
.handle_thread_request_result(
agent_id,
state,
state
.send_op(agent_id, Op::InterAgentCommunication { communication })
.await,
)
.await;
if result.is_ok() {
match last_task_message {