@@ -128,7 +128,28 @@ struct PreparedAgentTurn<State: Send + Sync> {
128128 run_id : crate :: ids:: RunId ,
129129 input_text : String ,
130130 messages : Vec < tinyinference_llm:: message:: Message > ,
131- progress : Option < tokio:: sync:: mpsc:: Sender < ProgressEvent > > ,
131+ progress : Option < ProgressSender > ,
132+ }
133+
134+ #[ derive( Clone ) ]
135+ pub ( crate ) struct ProgressSender {
136+ tx : tokio:: sync:: mpsc:: Sender < ProgressEvent > ,
137+ nonterminal_slots : std:: sync:: Arc < tokio:: sync:: Semaphore > ,
138+ }
139+
140+ impl ProgressSender {
141+ fn send_nonterminal ( & self , event : ProgressEvent ) {
142+ let Ok ( permit) = self . nonterminal_slots . clone ( ) . try_acquire_owned ( ) else {
143+ return ;
144+ } ;
145+ if self . tx . try_send ( event) . is_ok ( ) {
146+ permit. forget ( ) ;
147+ }
148+ }
149+
150+ fn send_terminal ( & self , event : ProgressEvent ) {
151+ let _ = self . tx . try_send ( event) ;
152+ }
132153}
133154
134155impl < State : Send + Sync > Clone for PreparedAgentTurn < State > {
@@ -450,11 +471,7 @@ impl<State: Send + Sync, Ctx: Send + Sync> AgentHarness<State, Ctx> {
450471 let Some ( progress) = binding. progress else {
451472 return ;
452473 } ;
453- if progress. try_send ( event) . is_err ( ) {
454- tinyagents_tracing:: debug!(
455- "[host] dropping progress event because the bounded queue is full or closed"
456- ) ;
457- }
474+ progress. send_nonterminal ( event) ;
458475 }
459476}
460477
@@ -484,12 +501,12 @@ async fn finish_host_turn<State: Send + Sync>(
484501 // consumer completely outside the agent's critical path.
485502 if let Some ( progress) = & prepared. progress {
486503 if let Some ( message) = error {
487- let _ = progress. try_send ( ProgressEvent :: Error {
504+ progress. send_terminal ( ProgressEvent :: Error {
488505 run : prepared. run_id . clone ( ) ,
489506 message,
490507 } ) ;
491508 } else {
492- let _ = progress. try_send ( ProgressEvent :: Finished {
509+ progress. send_terminal ( ProgressEvent :: Finished {
493510 run : prepared. run_id . clone ( ) ,
494511 usage : Some ( run. usage . usage ) ,
495512 } ) ;
@@ -533,21 +550,29 @@ async fn finish_host_turn<State: Send + Sync>(
533550
534551fn start_progress_dispatcher (
535552 sink : Option < std:: sync:: Arc < dyn crate :: host:: ProgressSink > > ,
536- ) -> Option < tokio :: sync :: mpsc :: Sender < ProgressEvent > > {
553+ ) -> Option < ProgressSender > {
537554 let sink = sink?;
538555 let handle = tokio:: runtime:: Handle :: try_current ( ) . ok ( ) ?;
539556 // Progress is observational. Bound it so a slow sink cannot retain every
540557 // streamed token; producers use `try_send` and drop overflowed updates.
541558 // One slot is reserved for the single terminal outcome. Producers use
542559 // `try_send` for ordinary progress, so at most 128 nonterminal events can
543560 // fill before finalization claims the remaining slot.
544- let ( tx, mut rx) = tokio:: sync:: mpsc:: channel ( 129 ) ;
561+ let ( tx, mut rx) = tokio:: sync:: mpsc:: channel :: < ProgressEvent > ( 129 ) ;
562+ let nonterminal_slots = std:: sync:: Arc :: new ( tokio:: sync:: Semaphore :: new ( 128 ) ) ;
563+ let released_slots = nonterminal_slots. clone ( ) ;
545564 handle. spawn ( async move {
546565 while let Some ( event) = rx. recv ( ) . await {
566+ if !event. is_terminal ( ) {
567+ released_slots. add_permits ( 1 ) ;
568+ }
547569 sink. emit ( event) . await ;
548570 }
549571 } ) ;
550- Some ( tx)
572+ Some ( ProgressSender {
573+ tx,
574+ nonterminal_slots,
575+ } )
551576}
552577
553578async fn screen_user_messages < State : Send + Sync > (
0 commit comments