@@ -1989,7 +1989,10 @@ impl ActorLogic<BusMessage> for AgentLogic {
19891989 h. cancel_children_for_parent ( & chat_id) ;
19901990 }
19911991 }
1992- if let Some ( ( _, token) ) = self . cancellation_tokens . remove ( & chat_id) {
1992+ // Keep ownership registered until the reasoning task emits its
1993+ // terminal lifecycle event and finalizes. New inbound arriving
1994+ // during cancellation must queue behind that acknowledgement.
1995+ if let Some ( token) = self . cancellation_tokens . get ( & chat_id) {
19931996 token. cancel ( ) ;
19941997 let _ = self . logger_tx . send ( BusMessage :: Log (
19951998 LogEvent :: info (
@@ -5328,6 +5331,69 @@ mod tests {
53285331 ) ;
53295332 }
53305333
5334+ #[ tokio:: test]
5335+ async fn inbound_after_cancel_waits_for_old_terminal_before_new_start ( ) {
5336+ let calls = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
5337+ let ( mut agent, mut outbound_rx) =
5338+ build_agent_with_provider ( Box :: new ( LongSleepProvider { calls } ) ) ;
5339+ let chat_id = "cancel-serialization-chat" ;
5340+ agent
5341+ . process ( BusMessage :: Inbound ( test_inbound ( chat_id, "first" ) ) )
5342+ . await
5343+ . expect ( "start first run" ) ;
5344+
5345+ let first_run_id = loop {
5346+ match outbound_rx. recv ( ) . await {
5347+ Some ( BusMessage :: RunLifecycle ( RunLifecycleEvent :: Started {
5348+ run_id,
5349+ chat_id : event_chat,
5350+ } ) ) if event_chat == chat_id => break run_id,
5351+ Some ( _) => continue ,
5352+ None => panic ! ( "outbound channel closed before first start" ) ,
5353+ }
5354+ } ;
5355+
5356+ agent
5357+ . process ( BusMessage :: Cancel ( chat_id. to_string ( ) ) )
5358+ . await
5359+ . expect ( "cancel accepted" ) ;
5360+ agent
5361+ . process ( BusMessage :: Inbound ( test_inbound ( chat_id, "second" ) ) )
5362+ . await
5363+ . expect ( "queue second run while cancellation unwinds" ) ;
5364+
5365+ let first_after_cancel = loop {
5366+ match outbound_rx. recv ( ) . await {
5367+ Some ( BusMessage :: RunLifecycle ( event) ) => break event,
5368+ Some ( _) => continue ,
5369+ None => panic ! ( "outbound channel closed during cancellation" ) ,
5370+ }
5371+ } ;
5372+ assert ! ( matches!(
5373+ first_after_cancel,
5374+ RunLifecycleEvent :: Terminated {
5375+ run_id,
5376+ chat_id: event_chat,
5377+ outcome: RunOutcome :: Cancelled ,
5378+ } if run_id == first_run_id && event_chat == chat_id
5379+ ) ) ;
5380+
5381+ let second_start = loop {
5382+ match outbound_rx. recv ( ) . await {
5383+ Some ( BusMessage :: RunLifecycle ( event @ RunLifecycleEvent :: Started { .. } ) ) => {
5384+ break event;
5385+ }
5386+ Some ( _) => continue ,
5387+ None => panic ! ( "outbound channel closed before second start" ) ,
5388+ }
5389+ } ;
5390+ assert ! ( matches!(
5391+ second_start,
5392+ RunLifecycleEvent :: Started { run_id, chat_id: event_chat }
5393+ if run_id != first_run_id && event_chat == chat_id
5394+ ) ) ;
5395+ }
5396+
53315397 #[ tokio:: test]
53325398 async fn clarification_inbound_routes_via_hub_before_reasoning_spawn ( ) {
53335399 let hub = Arc :: new ( ClarificationHub :: new ( ) ) ;
0 commit comments