camel-ai 0.2.73a4__py3-none-any.whl → 0.2.80a2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/_utils.py +38 -0
  3. camel/agents/chat_agent.py +2217 -519
  4. camel/agents/mcp_agent.py +30 -27
  5. camel/configs/__init__.py +15 -0
  6. camel/configs/aihubmix_config.py +88 -0
  7. camel/configs/amd_config.py +70 -0
  8. camel/configs/cometapi_config.py +104 -0
  9. camel/configs/minimax_config.py +93 -0
  10. camel/configs/nebius_config.py +103 -0
  11. camel/data_collectors/alpaca_collector.py +15 -6
  12. camel/datasets/base_generator.py +39 -10
  13. camel/environments/single_step.py +28 -3
  14. camel/environments/tic_tac_toe.py +1 -1
  15. camel/interpreters/__init__.py +2 -0
  16. camel/interpreters/docker/Dockerfile +3 -12
  17. camel/interpreters/e2b_interpreter.py +34 -1
  18. camel/interpreters/microsandbox_interpreter.py +395 -0
  19. camel/loaders/__init__.py +11 -2
  20. camel/loaders/chunkr_reader.py +9 -0
  21. camel/memories/agent_memories.py +48 -4
  22. camel/memories/base.py +26 -0
  23. camel/memories/blocks/chat_history_block.py +122 -4
  24. camel/memories/context_creators/score_based.py +25 -384
  25. camel/memories/records.py +88 -8
  26. camel/messages/base.py +153 -34
  27. camel/models/__init__.py +10 -0
  28. camel/models/aihubmix_model.py +83 -0
  29. camel/models/aiml_model.py +1 -16
  30. camel/models/amd_model.py +101 -0
  31. camel/models/anthropic_model.py +6 -19
  32. camel/models/aws_bedrock_model.py +2 -33
  33. camel/models/azure_openai_model.py +114 -89
  34. camel/models/base_audio_model.py +3 -1
  35. camel/models/base_model.py +32 -14
  36. camel/models/cohere_model.py +1 -16
  37. camel/models/cometapi_model.py +83 -0
  38. camel/models/crynux_model.py +1 -16
  39. camel/models/deepseek_model.py +1 -16
  40. camel/models/fish_audio_model.py +6 -0
  41. camel/models/gemini_model.py +36 -18
  42. camel/models/groq_model.py +1 -17
  43. camel/models/internlm_model.py +1 -16
  44. camel/models/litellm_model.py +1 -16
  45. camel/models/lmstudio_model.py +1 -17
  46. camel/models/minimax_model.py +83 -0
  47. camel/models/mistral_model.py +1 -16
  48. camel/models/model_factory.py +27 -1
  49. camel/models/modelscope_model.py +1 -16
  50. camel/models/moonshot_model.py +105 -24
  51. camel/models/nebius_model.py +83 -0
  52. camel/models/nemotron_model.py +0 -5
  53. camel/models/netmind_model.py +1 -16
  54. camel/models/novita_model.py +1 -16
  55. camel/models/nvidia_model.py +1 -16
  56. camel/models/ollama_model.py +4 -19
  57. camel/models/openai_compatible_model.py +62 -41
  58. camel/models/openai_model.py +62 -57
  59. camel/models/openrouter_model.py +1 -17
  60. camel/models/ppio_model.py +1 -16
  61. camel/models/qianfan_model.py +1 -16
  62. camel/models/qwen_model.py +1 -16
  63. camel/models/reka_model.py +1 -16
  64. camel/models/samba_model.py +34 -47
  65. camel/models/sglang_model.py +64 -31
  66. camel/models/siliconflow_model.py +1 -16
  67. camel/models/stub_model.py +0 -4
  68. camel/models/togetherai_model.py +1 -16
  69. camel/models/vllm_model.py +1 -16
  70. camel/models/volcano_model.py +0 -17
  71. camel/models/watsonx_model.py +1 -16
  72. camel/models/yi_model.py +1 -16
  73. camel/models/zhipuai_model.py +60 -16
  74. camel/parsers/__init__.py +18 -0
  75. camel/parsers/mcp_tool_call_parser.py +176 -0
  76. camel/retrievers/auto_retriever.py +1 -0
  77. camel/runtimes/daytona_runtime.py +11 -12
  78. camel/societies/__init__.py +2 -0
  79. camel/societies/workforce/__init__.py +2 -0
  80. camel/societies/workforce/events.py +122 -0
  81. camel/societies/workforce/prompts.py +146 -66
  82. camel/societies/workforce/role_playing_worker.py +15 -11
  83. camel/societies/workforce/single_agent_worker.py +302 -65
  84. camel/societies/workforce/structured_output_handler.py +30 -18
  85. camel/societies/workforce/task_channel.py +163 -27
  86. camel/societies/workforce/utils.py +107 -13
  87. camel/societies/workforce/workflow_memory_manager.py +772 -0
  88. camel/societies/workforce/workforce.py +1949 -579
  89. camel/societies/workforce/workforce_callback.py +74 -0
  90. camel/societies/workforce/workforce_logger.py +168 -145
  91. camel/societies/workforce/workforce_metrics.py +33 -0
  92. camel/storages/key_value_storages/json.py +15 -2
  93. camel/storages/key_value_storages/mem0_cloud.py +48 -47
  94. camel/storages/object_storages/google_cloud.py +1 -1
  95. camel/storages/vectordb_storages/oceanbase.py +13 -13
  96. camel/storages/vectordb_storages/qdrant.py +3 -3
  97. camel/storages/vectordb_storages/tidb.py +8 -6
  98. camel/tasks/task.py +4 -3
  99. camel/toolkits/__init__.py +20 -7
  100. camel/toolkits/aci_toolkit.py +45 -0
  101. camel/toolkits/base.py +6 -4
  102. camel/toolkits/code_execution.py +28 -1
  103. camel/toolkits/context_summarizer_toolkit.py +684 -0
  104. camel/toolkits/dappier_toolkit.py +5 -1
  105. camel/toolkits/dingtalk.py +1135 -0
  106. camel/toolkits/edgeone_pages_mcp_toolkit.py +11 -31
  107. camel/toolkits/excel_toolkit.py +1 -1
  108. camel/toolkits/{file_write_toolkit.py → file_toolkit.py} +430 -36
  109. camel/toolkits/function_tool.py +13 -3
  110. camel/toolkits/github_toolkit.py +104 -17
  111. camel/toolkits/gmail_toolkit.py +1839 -0
  112. camel/toolkits/google_calendar_toolkit.py +38 -4
  113. camel/toolkits/google_drive_mcp_toolkit.py +12 -31
  114. camel/toolkits/hybrid_browser_toolkit/config_loader.py +15 -0
  115. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +77 -8
  116. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +884 -88
  117. camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
  118. camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +5 -612
  119. camel/toolkits/hybrid_browser_toolkit/ts/package.json +0 -1
  120. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +959 -89
  121. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +9 -2
  122. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +281 -213
  123. camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
  124. camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
  125. camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
  126. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +23 -3
  127. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +72 -7
  128. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +582 -132
  129. camel/toolkits/hybrid_browser_toolkit_py/actions.py +158 -0
  130. camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +55 -8
  131. camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +43 -0
  132. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +321 -8
  133. camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +10 -4
  134. camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +45 -4
  135. camel/toolkits/{openai_image_toolkit.py → image_generation_toolkit.py} +151 -53
  136. camel/toolkits/klavis_toolkit.py +5 -1
  137. camel/toolkits/markitdown_toolkit.py +27 -1
  138. camel/toolkits/math_toolkit.py +64 -10
  139. camel/toolkits/mcp_toolkit.py +366 -71
  140. camel/toolkits/memory_toolkit.py +5 -1
  141. camel/toolkits/message_integration.py +18 -13
  142. camel/toolkits/minimax_mcp_toolkit.py +195 -0
  143. camel/toolkits/note_taking_toolkit.py +19 -10
  144. camel/toolkits/notion_mcp_toolkit.py +16 -26
  145. camel/toolkits/openbb_toolkit.py +5 -1
  146. camel/toolkits/origene_mcp_toolkit.py +8 -49
  147. camel/toolkits/playwright_mcp_toolkit.py +12 -31
  148. camel/toolkits/resend_toolkit.py +168 -0
  149. camel/toolkits/search_toolkit.py +264 -91
  150. camel/toolkits/slack_toolkit.py +64 -10
  151. camel/toolkits/terminal_toolkit/__init__.py +18 -0
  152. camel/toolkits/terminal_toolkit/terminal_toolkit.py +957 -0
  153. camel/toolkits/terminal_toolkit/utils.py +532 -0
  154. camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
  155. camel/toolkits/video_analysis_toolkit.py +17 -11
  156. camel/toolkits/wechat_official_toolkit.py +483 -0
  157. camel/toolkits/zapier_toolkit.py +5 -1
  158. camel/types/__init__.py +2 -2
  159. camel/types/enums.py +274 -7
  160. camel/types/openai_types.py +2 -2
  161. camel/types/unified_model_type.py +15 -0
  162. camel/utils/commons.py +36 -5
  163. camel/utils/constants.py +3 -0
  164. camel/utils/context_utils.py +1003 -0
  165. camel/utils/mcp.py +138 -4
  166. camel/utils/token_counting.py +43 -20
  167. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/METADATA +223 -83
  168. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/RECORD +170 -141
  169. camel/loaders/pandas_reader.py +0 -368
  170. camel/toolkits/openai_agent_toolkit.py +0 -135
  171. camel/toolkits/terminal_toolkit.py +0 -1550
  172. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/WHEEL +0 -0
  173. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/licenses/LICENSE +0 -0
@@ -119,11 +119,13 @@ class RolePlayingWorker(Worker):
119
119
  `TaskState.FAILED`.
120
120
  """
121
121
  dependency_tasks_info = self._get_dep_tasks_info(dependencies)
122
- prompt = ROLEPLAY_PROCESS_TASK_PROMPT.format(
123
- content=task.content,
124
- parent_task_content=task.parent.content if task.parent else "",
125
- dependency_tasks_info=dependency_tasks_info,
126
- additional_info=task.additional_info,
122
+ prompt = str(
123
+ ROLEPLAY_PROCESS_TASK_PROMPT.format(
124
+ content=task.content,
125
+ parent_task_content=task.parent.content if task.parent else "",
126
+ dependency_tasks_info=dependency_tasks_info,
127
+ additional_info=task.additional_info,
128
+ )
127
129
  )
128
130
  role_play_session = RolePlaying(
129
131
  assistant_role_name=self.assistant_role_name,
@@ -183,12 +185,14 @@ class RolePlayingWorker(Worker):
183
185
  input_msg = assistant_response.msg
184
186
 
185
187
  chat_history_str = "\n".join(chat_history)
186
- prompt = ROLEPLAY_SUMMARIZE_PROMPT.format(
187
- user_role=self.user_role_name,
188
- assistant_role=self.assistant_role_name,
189
- content=task.content,
190
- chat_history=chat_history_str,
191
- additional_info=task.additional_info,
188
+ prompt = str(
189
+ ROLEPLAY_SUMMARIZE_PROMPT.format(
190
+ user_role=self.user_role_name,
191
+ assistant_role=self.assistant_role_name,
192
+ content=task.content,
193
+ chat_history=chat_history_str,
194
+ additional_info=task.additional_info,
195
+ )
192
196
  )
193
197
  if self.use_structured_output_handler and self.structured_handler:
194
198
  # Use structured output handler for prompt-based extraction
@@ -17,26 +17,33 @@ import asyncio
17
17
  import datetime
18
18
  import time
19
19
  from collections import deque
20
- from typing import Any, List, Optional
20
+ from typing import Any, Dict, List, Optional
21
21
 
22
22
  from colorama import Fore
23
23
 
24
24
  from camel.agents import ChatAgent
25
25
  from camel.agents.chat_agent import AsyncStreamingChatAgentResponse
26
+ from camel.logger import get_logger
26
27
  from camel.societies.workforce.prompts import PROCESS_TASK_PROMPT
27
28
  from camel.societies.workforce.structured_output_handler import (
28
29
  StructuredOutputHandler,
29
30
  )
30
31
  from camel.societies.workforce.utils import TaskResult
31
32
  from camel.societies.workforce.worker import Worker
33
+ from camel.societies.workforce.workflow_memory_manager import (
34
+ WorkflowMemoryManager,
35
+ )
32
36
  from camel.tasks.task import Task, TaskState, is_task_result_insufficient
37
+ from camel.utils.context_utils import ContextUtility
38
+
39
+ logger = get_logger(__name__)
33
40
 
34
41
 
35
42
  class AgentPool:
36
43
  r"""A pool of agent instances for efficient reuse.
37
44
 
38
- This pool manages a collection of pre-cloned agents. It supports
39
- auto-scaling based ondemand and intelligent reuse of existing agents.
45
+ This pool manages a collection of pre-cloned agents with automatic
46
+ scaling and idle timeout cleanup.
40
47
 
41
48
  Args:
42
49
  base_agent (ChatAgent): The base agent to clone from.
@@ -48,6 +55,8 @@ class AgentPool:
48
55
  (default: :obj:`True`)
49
56
  idle_timeout (float): Time in seconds after which idle agents are
50
57
  removed. (default: :obj:`180.0`)
58
+ cleanup_interval (float): Fixed interval in seconds between cleanup
59
+ checks. (default: :obj:`60.0`)
51
60
  """
52
61
 
53
62
  def __init__(
@@ -56,23 +65,27 @@ class AgentPool:
56
65
  initial_size: int = 1,
57
66
  max_size: int = 10,
58
67
  auto_scale: bool = True,
59
- idle_timeout: float = 180.0, # 3 minutes
68
+ idle_timeout: float = 180.0,
69
+ cleanup_interval: float = 60.0,
60
70
  ):
61
71
  self.base_agent = base_agent
62
72
  self.max_size = max_size
63
73
  self.auto_scale = auto_scale
64
74
  self.idle_timeout = idle_timeout
75
+ self.cleanup_interval = cleanup_interval
65
76
 
66
77
  # Pool management
67
78
  self._available_agents: deque = deque()
68
79
  self._in_use_agents: set = set()
69
80
  self._agent_last_used: dict = {}
70
81
  self._lock = asyncio.Lock()
82
+ self._condition = asyncio.Condition(self._lock)
71
83
 
72
84
  # Statistics
73
85
  self._total_borrows = 0
74
86
  self._total_clones_created = 0
75
87
  self._pool_hits = 0
88
+ self._agents_cleaned = 0
76
89
 
77
90
  # Initialize pool
78
91
  self._initialize_pool(initial_size)
@@ -82,6 +95,7 @@ class AgentPool:
82
95
  for _ in range(min(size, self.max_size)):
83
96
  agent = self._create_fresh_agent()
84
97
  self._available_agents.append(agent)
98
+ self._agent_last_used[id(agent)] = time.time()
85
99
 
86
100
  def _create_fresh_agent(self) -> ChatAgent:
87
101
  r"""Create a fresh agent instance."""
@@ -91,87 +105,82 @@ class AgentPool:
91
105
 
92
106
  async def get_agent(self) -> ChatAgent:
93
107
  r"""Get an agent from the pool, creating one if necessary."""
94
- async with self._lock:
108
+ async with self._condition:
95
109
  self._total_borrows += 1
96
110
 
97
- # Try to get from available agents first
98
- if self._available_agents:
99
- agent = self._available_agents.popleft()
100
- self._in_use_agents.add(id(agent))
101
- self._pool_hits += 1
111
+ # Try to get available agent or create new one
112
+ while True:
113
+ if self._available_agents:
114
+ agent = self._available_agents.popleft()
115
+ self._in_use_agents.add(id(agent))
116
+ self._pool_hits += 1
117
+ return agent
102
118
 
103
- # Reset the agent state
104
- agent.reset()
105
- return agent
119
+ # Check if we can create a new agent
120
+ if len(self._in_use_agents) < self.max_size or self.auto_scale:
121
+ agent = self._create_fresh_agent()
122
+ self._in_use_agents.add(id(agent))
123
+ return agent
106
124
 
107
- # Check if we can create new agents
108
- total_agents = len(self._available_agents) + len(
109
- self._in_use_agents
110
- )
111
- if total_agents < self.max_size:
112
- agent = self._create_fresh_agent()
113
- self._in_use_agents.add(id(agent))
114
- return agent
115
-
116
- # Pool exhausted, wait and retry or create temporary agent
117
- if self.auto_scale:
118
- # Create a temporary agent that won't be returned to pool
119
- return self._create_fresh_agent()
120
- else:
121
- # Wait for an agent to become available
122
- while not self._available_agents:
123
- await asyncio.sleep(0.1)
124
-
125
- agent = self._available_agents.popleft()
126
- self._in_use_agents.add(id(agent))
127
- agent.reset()
128
- return agent
125
+ # Wait for an agent to be returned
126
+ await self._condition.wait()
129
127
 
130
128
  async def return_agent(self, agent: ChatAgent) -> None:
131
129
  r"""Return an agent to the pool."""
132
- async with self._lock:
133
- agent_id = id(agent)
130
+ agent_id = id(agent)
134
131
 
135
- if agent_id in self._in_use_agents:
136
- self._in_use_agents.remove(agent_id)
132
+ async with self._condition:
133
+ if agent_id not in self._in_use_agents:
134
+ return
137
135
 
138
- # Only return to pool if we're under max size
139
- if len(self._available_agents) < self.max_size:
140
- # Reset agent state before returning to pool
141
- agent.reset()
142
- self._available_agents.append(agent)
143
- self._agent_last_used[agent_id] = time.time()
136
+ self._in_use_agents.discard(agent_id)
137
+
138
+ # Only add back to pool if under max size
139
+ if len(self._available_agents) < self.max_size:
140
+ agent.reset()
141
+ self._agent_last_used[agent_id] = time.time()
142
+ self._available_agents.append(agent)
143
+ # Notify one waiting coroutine that an agent is available
144
+ self._condition.notify()
145
+ else:
146
+ # Remove tracking for agents not returned to pool
147
+ self._agent_last_used.pop(agent_id, None)
144
148
 
145
149
  async def cleanup_idle_agents(self) -> None:
146
150
  r"""Remove idle agents from the pool to free memory."""
147
151
  if not self.auto_scale:
148
152
  return
149
153
 
150
- async with self._lock:
154
+ async with self._condition:
155
+ if not self._available_agents:
156
+ return
157
+
151
158
  current_time = time.time()
152
159
  agents_to_remove = []
153
160
 
154
161
  for agent in list(self._available_agents):
155
162
  agent_id = id(agent)
156
163
  last_used = self._agent_last_used.get(agent_id, current_time)
157
-
158
164
  if current_time - last_used > self.idle_timeout:
159
165
  agents_to_remove.append(agent)
160
166
 
161
167
  for agent in agents_to_remove:
162
168
  self._available_agents.remove(agent)
163
- agent_id = id(agent)
164
- self._agent_last_used.pop(agent_id, None)
169
+ self._agent_last_used.pop(id(agent), None)
170
+ self._agents_cleaned += 1
165
171
 
166
172
  def get_stats(self) -> dict:
167
173
  r"""Get pool statistics."""
168
174
  return {
169
175
  "available_agents": len(self._available_agents),
170
176
  "in_use_agents": len(self._in_use_agents),
177
+ "pool_size": len(self._available_agents)
178
+ + len(self._in_use_agents),
171
179
  "total_borrows": self._total_borrows,
172
180
  "total_clones_created": self._total_clones_created,
173
181
  "pool_hits": self._pool_hits,
174
182
  "hit_rate": self._pool_hits / max(self._total_borrows, 1),
183
+ "agents_cleaned_up": self._agents_cleaned,
175
184
  }
176
185
 
177
186
 
@@ -197,6 +206,16 @@ class SingleAgentWorker(Worker):
197
206
  support native structured output. When disabled, the workforce
198
207
  uses the native response_format parameter.
199
208
  (default: :obj:`True`)
209
+ context_utility (ContextUtility, optional): Shared context utility
210
+ instance for workflow management. If provided, all workflow
211
+ operations will use this shared instance instead of creating
212
+ a new one. This ensures multiple workers share the same session
213
+ directory. (default: :obj:`None`)
214
+ enable_workflow_memory (bool, optional): Whether to enable workflow
215
+ memory accumulation during task execution. When enabled,
216
+ conversations from all task executions are accumulated for
217
+ potential workflow saving. Set to True if you plan to call
218
+ save_workflow_memories(). (default: :obj:`False`)
200
219
  """
201
220
 
202
221
  def __init__(
@@ -208,6 +227,8 @@ class SingleAgentWorker(Worker):
208
227
  pool_max_size: int = 10,
209
228
  auto_scale_pool: bool = True,
210
229
  use_structured_output_handler: bool = True,
230
+ context_utility: Optional[ContextUtility] = None,
231
+ enable_workflow_memory: bool = False,
211
232
  ) -> None:
212
233
  node_id = worker.agent_id
213
234
  super().__init__(
@@ -222,6 +243,21 @@ class SingleAgentWorker(Worker):
222
243
  )
223
244
  self.worker = worker
224
245
  self.use_agent_pool = use_agent_pool
246
+ self.enable_workflow_memory = enable_workflow_memory
247
+ self._shared_context_utility = context_utility
248
+ self._context_utility: Optional[ContextUtility] = (
249
+ None # Will be initialized when needed
250
+ )
251
+
252
+ # accumulator agent for collecting conversations
253
+ # from all task processing
254
+ self._conversation_accumulator: Optional[ChatAgent] = None
255
+
256
+ # workflow memory manager for handling workflow operations
257
+ self._workflow_manager: Optional[WorkflowMemoryManager] = None
258
+
259
+ # note: context utility is set on the worker agent during save/load
260
+ # operations to avoid creating session folders during initialization
225
261
 
226
262
  self.agent_pool: Optional[AgentPool] = None
227
263
  self._cleanup_task: Optional[asyncio.Task] = None
@@ -264,6 +300,39 @@ class SingleAgentWorker(Worker):
264
300
  await self.agent_pool.return_agent(agent)
265
301
  # If not using pool, agent will be garbage collected
266
302
 
303
+ def _get_context_utility(self) -> ContextUtility:
304
+ r"""Get context utility with lazy initialization."""
305
+ if self._context_utility is None:
306
+ self._context_utility = (
307
+ self._shared_context_utility
308
+ or ContextUtility.get_workforce_shared()
309
+ )
310
+ return self._context_utility
311
+
312
+ def _get_conversation_accumulator(self) -> ChatAgent:
313
+ r"""Get or create the conversation accumulator agent."""
314
+ if self._conversation_accumulator is None:
315
+ # create a clone of the original worker to serve as accumulator
316
+ self._conversation_accumulator = self.worker.clone(
317
+ with_memory=False
318
+ )
319
+ return self._conversation_accumulator
320
+
321
+ def _get_workflow_manager(self) -> WorkflowMemoryManager:
322
+ r"""Get or create the workflow memory manager."""
323
+ if self._workflow_manager is None:
324
+ context_util = (
325
+ self._shared_context_utility
326
+ if self._shared_context_utility is not None
327
+ else None
328
+ )
329
+ self._workflow_manager = WorkflowMemoryManager(
330
+ worker=self.worker,
331
+ description=self.description,
332
+ context_utility=context_util,
333
+ )
334
+ return self._workflow_manager
335
+
267
336
  async def _process_task(
268
337
  self, task: Task, dependencies: List[Task]
269
338
  ) -> TaskState:
@@ -290,11 +359,15 @@ class SingleAgentWorker(Worker):
290
359
 
291
360
  try:
292
361
  dependency_tasks_info = self._get_dep_tasks_info(dependencies)
293
- prompt = PROCESS_TASK_PROMPT.format(
294
- content=task.content,
295
- parent_task_content=task.parent.content if task.parent else "",
296
- dependency_tasks_info=dependency_tasks_info,
297
- additional_info=task.additional_info,
362
+ prompt = str(
363
+ PROCESS_TASK_PROMPT.format(
364
+ content=task.content,
365
+ parent_task_content=task.parent.content
366
+ if task.parent
367
+ else "",
368
+ dependency_tasks_info=dependency_tasks_info,
369
+ additional_info=task.additional_info,
370
+ )
298
371
  )
299
372
 
300
373
  if self.use_structured_output_handler and self.structured_handler:
@@ -374,6 +447,7 @@ class SingleAgentWorker(Worker):
374
447
  "usage"
375
448
  ) or final_response.info.get("token_usage")
376
449
  else:
450
+ final_response = response
377
451
  usage_info = response.info.get("usage") or response.info.get(
378
452
  "token_usage"
379
453
  )
@@ -381,10 +455,36 @@ class SingleAgentWorker(Worker):
381
455
  usage_info.get("total_tokens", 0) if usage_info else 0
382
456
  )
383
457
 
458
+ # collect conversation from working agent to
459
+ # accumulator for workflow memory
460
+ # Only transfer memory if workflow memory is enabled
461
+ if self.enable_workflow_memory:
462
+ accumulator = self._get_conversation_accumulator()
463
+
464
+ # transfer all memory records from working agent to accumulator
465
+ try:
466
+ # retrieve all context records from the working agent
467
+ work_records = worker_agent.memory.retrieve()
468
+
469
+ # write these records to the accumulator's memory
470
+ memory_records = [
471
+ record.memory_record for record in work_records
472
+ ]
473
+ accumulator.memory.write_records(memory_records)
474
+
475
+ logger.debug(
476
+ f"Transferred {len(memory_records)} memory records to "
477
+ f"accumulator"
478
+ )
479
+
480
+ except Exception as e:
481
+ logger.warning(
482
+ f"Failed to transfer conversation to accumulator: {e}"
483
+ )
484
+
384
485
  except Exception as e:
385
- print(
386
- f"{Fore.RED}Error processing task {task.id}: "
387
- f"{type(e).__name__}: {e}{Fore.RESET}"
486
+ logger.error(
487
+ f"Error processing task {task.id}: {type(e).__name__}: {e}"
388
488
  )
389
489
  # Store error information in task result
390
490
  task.result = f"{type(e).__name__}: {e!s}"
@@ -429,13 +529,13 @@ class SingleAgentWorker(Worker):
429
529
  task.additional_info["token_usage"] = {"total_tokens": total_tokens}
430
530
 
431
531
  print(f"======\n{Fore.GREEN}Response from {self}:{Fore.RESET}")
532
+ logger.info(f"Response from {self}:")
432
533
 
433
534
  if not self.use_structured_output_handler:
434
535
  # Handle native structured output parsing
435
536
  if task_result is None:
436
- print(
437
- f"{Fore.RED}Error in worker step execution: Invalid "
438
- f"task result{Fore.RESET}"
537
+ logger.error(
538
+ "Error in worker step execution: Invalid task result"
439
539
  )
440
540
  task_result = TaskResult(
441
541
  content="Failed to generate valid task result.",
@@ -446,6 +546,10 @@ class SingleAgentWorker(Worker):
446
546
  print(
447
547
  f"\n{color}{task_result.content}{Fore.RESET}\n======", # type: ignore[union-attr]
448
548
  )
549
+ if task_result.failed: # type: ignore[union-attr]
550
+ logger.error(f"{task_result.content}") # type: ignore[union-attr]
551
+ else:
552
+ logger.info(f"{task_result.content}") # type: ignore[union-attr]
449
553
 
450
554
  task.result = task_result.content # type: ignore[union-attr]
451
555
 
@@ -453,9 +557,9 @@ class SingleAgentWorker(Worker):
453
557
  return TaskState.FAILED
454
558
 
455
559
  if is_task_result_insufficient(task):
456
- print(
457
- f"{Fore.RED}Task {task.id}: Content validation failed - "
458
- f"task marked as failed{Fore.RESET}"
560
+ logger.warning(
561
+ f"Task {task.id}: Content validation failed - "
562
+ f"task marked as failed"
459
563
  )
460
564
  return TaskState.FAILED
461
565
  return TaskState.DONE
@@ -477,16 +581,149 @@ class SingleAgentWorker(Worker):
477
581
  r"""Periodically clean up idle agents from the pool."""
478
582
  while True:
479
583
  try:
480
- await asyncio.sleep(60) # Cleanup every minute
584
+ # Fixed interval cleanup
481
585
  if self.agent_pool:
586
+ await asyncio.sleep(self.agent_pool.cleanup_interval)
482
587
  await self.agent_pool.cleanup_idle_agents()
588
+ else:
589
+ break
483
590
  except asyncio.CancelledError:
484
591
  break
485
592
  except Exception as e:
486
- print(f"Error in pool cleanup: {e}")
593
+ logger.warning(f"Error in pool cleanup: {e}")
487
594
 
488
595
  def get_pool_stats(self) -> Optional[dict]:
489
596
  r"""Get agent pool statistics if pool is enabled."""
490
597
  if self.use_agent_pool and self.agent_pool:
491
598
  return self.agent_pool.get_stats()
492
599
  return None
600
+
601
+ def save_workflow_memories(self) -> Dict[str, Any]:
602
+ r"""Save the worker's current workflow memories using agent
603
+ summarization.
604
+
605
+ .. deprecated:: 0.2.80
606
+ Use :meth:`save_workflow_memories_async` for async/await support
607
+ and better integration with parallel workflow saving.
608
+
609
+ This method generates a workflow summary from the worker agent's
610
+ conversation history and saves it to a markdown file. The filename
611
+ is based on either the worker's explicit role_name or the generated
612
+ task_title from the summary.
613
+
614
+ Delegates to WorkflowMemoryManager for all workflow operations.
615
+
616
+ Returns:
617
+ Dict[str, Any]: Result dictionary with keys:
618
+ - status (str): "success" or "error"
619
+ - summary (str): Generated workflow summary
620
+ - file_path (str): Path to saved file
621
+ - worker_description (str): Worker description used
622
+
623
+ See Also:
624
+ :meth:`save_workflow_memories_async`: Async version for better
625
+ performance in parallel workflows.
626
+ """
627
+ import warnings
628
+
629
+ warnings.warn(
630
+ "save_workflow_memories() is synchronous. Consider using "
631
+ "save_workflow_memories_async() for async/await support.",
632
+ DeprecationWarning,
633
+ stacklevel=2,
634
+ )
635
+
636
+ manager = self._get_workflow_manager()
637
+ result = manager.save_workflow(
638
+ conversation_accumulator=self._conversation_accumulator
639
+ )
640
+
641
+ # clean up accumulator after successful save
642
+ if (
643
+ result.get("status") == "success"
644
+ and self._conversation_accumulator is not None
645
+ ):
646
+ logger.info(
647
+ "Cleaning up conversation accumulator after workflow "
648
+ "summarization"
649
+ )
650
+ self._conversation_accumulator = None
651
+
652
+ return result
653
+
654
+ async def save_workflow_memories_async(self) -> Dict[str, Any]:
655
+ r"""Asynchronously save the worker's current workflow memories using
656
+ agent summarization.
657
+
658
+ This is the async version of save_workflow_memories() that uses
659
+ asummarize() for non-blocking LLM calls, enabling parallel
660
+ summarization of multiple workers.
661
+
662
+ Delegates to WorkflowMemoryManager for all workflow operations.
663
+
664
+ Returns:
665
+ Dict[str, Any]: Result dictionary with keys:
666
+ - status (str): "success" or "error"
667
+ - summary (str): Generated workflow summary
668
+ - file_path (str): Path to saved file
669
+ - worker_description (str): Worker description used
670
+ """
671
+ manager = self._get_workflow_manager()
672
+ result = await manager.save_workflow_async(
673
+ conversation_accumulator=self._conversation_accumulator
674
+ )
675
+
676
+ # clean up accumulator after successful save
677
+ if (
678
+ result.get("status") == "success"
679
+ and self._conversation_accumulator is not None
680
+ ):
681
+ logger.info(
682
+ "Cleaning up conversation accumulator after workflow "
683
+ "summarization"
684
+ )
685
+ self._conversation_accumulator = None
686
+
687
+ return result
688
+
689
+ def load_workflow_memories(
690
+ self,
691
+ pattern: Optional[str] = None,
692
+ max_workflows: int = 3,
693
+ session_id: Optional[str] = None,
694
+ use_smart_selection: bool = True,
695
+ ) -> bool:
696
+ r"""Load workflow memories using intelligent agent-based selection.
697
+
698
+ This method uses the worker agent to intelligently select the most
699
+ relevant workflows based on metadata (title, description, tags)
700
+ rather than simple filename pattern matching.
701
+
702
+ Delegates to WorkflowMemoryManager for all workflow operations.
703
+
704
+ Args:
705
+ pattern (Optional[str]): Legacy parameter for backward
706
+ compatibility. When use_smart_selection=False, uses this
707
+ pattern for file matching. Ignored when smart selection
708
+ is enabled.
709
+ max_workflows (int): Maximum number of workflow files to load.
710
+ (default: :obj:`3`)
711
+ session_id (Optional[str]): Specific workforce session ID to load
712
+ from. If None, searches across all sessions.
713
+ (default: :obj:`None`)
714
+ use_smart_selection (bool): Whether to use agent-based intelligent
715
+ workflow selection. When True, uses metadata and LLM to select
716
+ most relevant workflows. When False, falls back to pattern
717
+ matching. (default: :obj:`True`)
718
+
719
+ Returns:
720
+ bool: True if workflow memories were successfully loaded, False
721
+ otherwise.
722
+ """
723
+ manager = self._get_workflow_manager()
724
+ return manager.load_workflows(
725
+ pattern=pattern,
726
+ max_files_to_load=max_workflows,
727
+ session_id=session_id,
728
+ use_smart_selection=use_smart_selection,
729
+ )