mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
core: surface terminal subagent errors to parent agents (#28375)
## Why
When a subagent exhausts its retries, it emits an `Error`, but the
generic task lifecycle then emits `TurnComplete(None)`. That completion
used to overwrite the subagent's `Errored` status with
`Completed(None)`, so the parent received an empty completion
notification.
This made a failed child look indistinguishable from a child that
completed without an answer. In unattended or long-running multi-agent
work, the root could silently continue without knowing that delegated
work failed or how to restart it.
## Behavior
Before, a terminal stream failure was reduced to an empty completion:
```text
<subagent_notification>
{"agent_path":"/root/worker","status":{"completed":null}}
</subagent_notification>
```
Now the parent receives the actual terminal error, bounded to 1,000
tokens, together with an actionable recovery hint:
```text
<subagent_notification>
{
"agent_path": "/root/worker",
"status": {
"errored": "stream disconnected before completion: stream closed before response.completed"
},
"next_action": "This agent's turn failed. If you still need this agent, use `followup_task` to give it another task."
}
</subagent_notification>
```
The notification remains queue-only: it does not wake the root or replay
the failed request. The root sees it at the next sampling boundary and
can use `followup_task` to start a new turn for that agent.
## What changed
- Added terminal-error precedence to the [agent status
reducer](https://github.com/openai/codex/blob/e95fcfe2bb6a02f1a75650afa20048859f556511/codex-rs/core/src/agent/status.rs#L23-L34),
so a closing `TurnComplete` cannot erase an immediately preceding
`Errored` status.
- Made MultiAgentV2 completion forwarding use the retained session
status instead of re-deriving `Completed(None)` from the final event.
- Extended the [subagent notification
fragment](https://github.com/openai/codex/blob/e95fcfe2bb6a02f1a75650afa20048859f556511/codex-rs/core/src/context/subagent_notification.rs#L6-L60)
with a `next_action` for terminal errors and a hard cap on model-visible
error text.
- Kept successful completions and interrupted turns unchanged.
## Verification
- Added a status-reducer test proving that `Errored` survives the
trailing `TurnComplete`.
- Added an integration test that exhausts a subagent's stream retries
and verifies the exact `agent_message` delivered to the parent,
including the error and `followup_task` guidance.
- Re-ran the existing successful-completion and interrupted-turn
notification tests.
This commit is contained in:
7 files changed
+104
-13
No files matched your search
@@ -167,6 +167,7 @@ pub struct TurnContext {
|
||||
pub(crate) extension_data: Arc<codex_extension_api::ExtensionData>,
|
||||
pub(crate) turn_skills: TurnSkillsContext,
|
||||
pub(crate) turn_timing_state: Arc<TurnTimingState>,
|
||||
pub(crate) terminal_error: Arc<Mutex<Option<String>>>,
|
||||
pub(crate) server_model_warning_emitted: AtomicBool,
|
||||
pub(crate) model_verification_emitted: AtomicBool,
|
||||
}
|
||||
@@ -336,6 +337,7 @@ impl TurnContext {
|
||||
extension_data: Arc::clone(&self.extension_data),
|
||||
turn_skills: self.turn_skills.clone(),
|
||||
turn_timing_state: Arc::clone(&self.turn_timing_state),
|
||||
terminal_error: Arc::clone(&self.terminal_error),
|
||||
server_model_warning_emitted: AtomicBool::new(
|
||||
self.server_model_warning_emitted.load(Ordering::Relaxed),
|
||||
),
|
||||
@@ -639,6 +641,7 @@ impl Session {
|
||||
extension_data,
|
||||
turn_skills: TurnSkillsContext::new(skills_outcome),
|
||||
turn_timing_state: Arc::new(TurnTimingState::default()),
|
||||
terminal_error: Arc::new(Mutex::new(None)),
|
||||
server_model_warning_emitted: AtomicBool::new(false),
|
||||
model_verification_emitted: AtomicBool::new(false),
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user