feat(supervisor): push agent completion notifications to the spawning context

The spawned-agent task now pushes an agent_completed/agent_failed event
into the spawning context's notification queue before returning, so a
parent that keeps working learns mid-turn that a child finished instead
of discovering it only at the turn-end guardrail. Cancelled or
already-collected agents are suppressed by the existing drain-time
registration filter. This delivery applies regardless of whether
background jobs are enabled.
This commit is contained in:
2026-08-25 17:52:19 -06:00
parent 2d874f1d7c
commit caabf41b65
3 changed files with 118 additions and 7 deletions
+40
View File
@@ -40,6 +40,20 @@ pub fn job_notification(id: &str, tool: &str, success: bool) -> SystemNotificati
}
}
pub fn agent_notification(id: &str, agent_name: &str, success: bool) -> SystemNotification {
SystemNotification {
event: if success {
"agent_completed"
} else {
"agent_failed"
},
id: id.to_string(),
tool_or_agent: agent_name.to_string(),
status: if success { "success" } else { "failed" },
next_action: format!("agent__collect --id {id} for output"),
}
}
/// Completion events for background work started by ONE context. Unlike the
/// escalation queue (shared, root-owned), every context owns a fresh queue:
/// a queue shared between parent and child would race their drains and
@@ -106,6 +120,32 @@ mod tests {
assert_eq!(event.next_action, "job__collect --id job_a1b2 for output");
}
#[test]
fn agent_notification_success_shape() {
let event = agent_notification("agent_explore_a1b2", "explore", true);
assert_eq!(
event.to_value(),
json!({
"event": "agent_completed",
"id": "agent_explore_a1b2",
"tool_or_agent": "explore",
"status": "success",
"next_action": "agent__collect --id agent_explore_a1b2 for output",
})
);
}
#[test]
fn agent_notification_failure_shape() {
let event = agent_notification("agent_explore_a1b2", "explore", false);
assert_eq!(event.event, "agent_failed");
assert_eq!(event.status, "failed");
assert_eq!(
event.next_action,
"agent__collect --id agent_explore_a1b2 for output"
);
}
#[test]
fn drain_empties_queue_and_preserves_order() {
let queue = NotificationQueue::new();