@@ -80,6 +80,7 @@ def __init__(
8080 self ._in_use_agents : set = set ()
8181 self ._agent_last_used : dict = {}
8282 self ._lock = asyncio .Lock ()
83+ self ._condition = asyncio .Condition (self ._lock )
8384
8485 # Statistics
8586 self ._total_borrows = 0
@@ -105,36 +106,31 @@ def _create_fresh_agent(self) -> ChatAgent:
105106
106107 async def get_agent (self ) -> ChatAgent :
107108 r"""Get an agent from the pool, creating one if necessary."""
108- async with self ._lock :
109+ async with self ._condition :
109110 self ._total_borrows += 1
110111
111- if self ._available_agents :
112- agent = self ._available_agents .popleft ()
113- self ._in_use_agents .add (id (agent ))
114- self ._pool_hits += 1
115- return agent
116-
117- # Check if we can create a new agent
118- if len (self ._in_use_agents ) < self .max_size or self .auto_scale :
119- agent = self ._create_fresh_agent ()
120- self ._in_use_agents .add (id (agent ))
121- return agent
122-
123- # Wait for available agent
124- while True :
125- async with self ._lock :
112+ # Try to get available agent or create new one
113+ while True :
126114 if self ._available_agents :
127115 agent = self ._available_agents .popleft ()
128116 self ._in_use_agents .add (id (agent ))
129117 self ._pool_hits += 1
130118 return agent
131- await asyncio .sleep (0.05 )
119+
120+ # Check if we can create a new agent
121+ if len (self ._in_use_agents ) < self .max_size or self .auto_scale :
122+ agent = self ._create_fresh_agent ()
123+ self ._in_use_agents .add (id (agent ))
124+ return agent
125+
126+ # Wait for an agent to be returned
127+ await self ._condition .wait ()
132128
133129 async def return_agent (self , agent : ChatAgent ) -> None :
134130 r"""Return an agent to the pool."""
135131 agent_id = id (agent )
136132
137- async with self ._lock :
133+ async with self ._condition :
138134 if agent_id not in self ._in_use_agents :
139135 return
140136
@@ -145,6 +141,8 @@ async def return_agent(self, agent: ChatAgent) -> None:
145141 agent .reset ()
146142 self ._agent_last_used [agent_id ] = time .time ()
147143 self ._available_agents .append (agent )
144+ # Notify one waiting coroutine that an agent is available
145+ self ._condition .notify ()
148146 else :
149147 # Remove tracking for agents not returned to pool
150148 self ._agent_last_used .pop (agent_id , None )
@@ -154,7 +152,7 @@ async def cleanup_idle_agents(self) -> None:
154152 if not self .auto_scale :
155153 return
156154
157- async with self ._lock :
155+ async with self ._condition :
158156 if not self ._available_agents :
159157 return
160158
@@ -428,6 +426,7 @@ async def _process_task(
428426 "usage"
429427 ) or final_response .info .get ("token_usage" )
430428 else :
429+ final_response = response
431430 usage_info = response .info .get ("usage" ) or response .info .get (
432431 "token_usage"
433432 )
@@ -562,10 +561,11 @@ async def _periodic_cleanup(self):
562561 while True :
563562 try :
564563 # Fixed interval cleanup
565- await asyncio .sleep (self .agent_pool .cleanup_interval )
566-
567564 if self .agent_pool :
565+ await asyncio .sleep (self .agent_pool .cleanup_interval )
568566 await self .agent_pool .cleanup_idle_agents ()
567+ else :
568+ break
569569 except asyncio .CancelledError :
570570 break
571571 except Exception as e :
@@ -583,7 +583,8 @@ def save_workflow_memories(self) -> Dict[str, Any]:
583583
584584 This method generates a workflow summary from the worker agent's
585585 conversation history and saves it to a markdown file. The filename
586- is based on the worker's description for easy loading later.
586+ is based on either the worker's explicit role_name or the generated
587+ task_title from the summary.
587588
588589 Returns:
589590 Dict[str, Any]: Result dictionary with keys:
@@ -603,13 +604,31 @@ def save_workflow_memories(self) -> Dict[str, Any]:
603604 self .worker .set_context_utility (context_util )
604605
605606 # prepare workflow summarization components
606- filename = self ._generate_workflow_filename ()
607607 structured_prompt = self ._prepare_workflow_prompt ()
608608 agent_to_summarize = self ._select_agent_for_summarization (
609609 context_util
610610 )
611611
612+ # check if we should use role_name or let summarize extract
613+ # task_title
614+ role_name = getattr (self .worker , 'role_name' , 'assistant' )
615+ use_role_name_for_filename = role_name .lower () not in {
616+ 'assistant' ,
617+ 'agent' ,
618+ 'user' ,
619+ 'system' ,
620+ }
621+
612622 # generate and save workflow summary
623+ # if role_name is explicit, use it for filename
624+ # if role_name is generic, pass none to let summarize use
625+ # task_title
626+ filename = (
627+ self ._generate_workflow_filename ()
628+ if use_role_name_for_filename
629+ else None
630+ )
631+
613632 result = agent_to_summarize .summarize (
614633 filename = filename ,
615634 summary_prompt = structured_prompt ,
@@ -716,12 +735,23 @@ def _find_workflow_files(
716735 )
717736 return []
718737
719- # generate filename-safe search pattern from worker description
738+ # generate filename-safe search pattern from worker role name
720739 if pattern is None :
721- # sanitize description: spaces to underscores, remove special chars
722- clean_desc = self .description .lower ().replace (" " , "_" )
723- clean_desc = re .sub (r'[^a-z0-9_]' , '' , clean_desc )
724- pattern = f"{ clean_desc } _workflow*.md"
740+ from camel .utils .context_utils import ContextUtility
741+
742+ # get role_name (always available, defaults to "assistant")
743+ role_name = getattr (self .worker , 'role_name' , 'assistant' )
744+ clean_name = ContextUtility .sanitize_workflow_filename (role_name )
745+
746+ # check if role_name is generic
747+ generic_names = {'assistant' , 'agent' , 'user' , 'system' }
748+ if clean_name in generic_names :
749+ # for generic role names, search for all workflow files
750+ # since filename is based on task_title
751+ pattern = "*_workflow*.md"
752+ else :
753+ # for explicit role names, search for role-specific files
754+ pattern = f"{ clean_name } _workflow*.md"
725755
726756 # Get the base workforce_workflows directory
727757 camel_workdir = os .environ .get ("CAMEL_WORKDIR" )
@@ -816,15 +846,21 @@ def _validate_workflow_save_requirements(self) -> Optional[Dict[str, Any]]:
816846 return None
817847
818848 def _generate_workflow_filename (self ) -> str :
819- r"""Generate a filename for the workflow based on worker description.
849+ r"""Generate a filename for the workflow based on worker role name.
850+
851+ Uses the worker's explicit role_name when available.
820852
821853 Returns:
822- str: Sanitized filename without timestamp (session already has
823- timestamp).
854+ str: Sanitized filename without timestamp and without .md
855+ extension. Format: {role_name}_workflow
824856 """
825- clean_desc = self .description .lower ().replace (" " , "_" )
826- clean_desc = re .sub (r'[^a-z0-9_]' , '' , clean_desc )
827- return f"{ clean_desc } _workflow"
857+ from camel .utils .context_utils import ContextUtility
858+
859+ # get role_name (always available, defaults to "assistant"/"Assistant")
860+ role_name = getattr (self .worker , 'role_name' , 'assistant' )
861+ clean_name = ContextUtility .sanitize_workflow_filename (role_name )
862+
863+ return f"{ clean_name } _workflow"
828864
829865 def _prepare_workflow_prompt (self ) -> str :
830866 r"""Prepare the structured prompt for workflow summarization.
0 commit comments