agno 2.3.24__py3-none-any.whl → 2.3.26__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 (70) hide show
  1. agno/agent/agent.py +357 -28
  2. agno/db/base.py +214 -0
  3. agno/db/dynamo/dynamo.py +47 -0
  4. agno/db/firestore/firestore.py +47 -0
  5. agno/db/gcs_json/gcs_json_db.py +47 -0
  6. agno/db/in_memory/in_memory_db.py +47 -0
  7. agno/db/json/json_db.py +47 -0
  8. agno/db/mongo/async_mongo.py +229 -0
  9. agno/db/mongo/mongo.py +47 -0
  10. agno/db/mongo/schemas.py +16 -0
  11. agno/db/mysql/async_mysql.py +47 -0
  12. agno/db/mysql/mysql.py +47 -0
  13. agno/db/postgres/async_postgres.py +231 -0
  14. agno/db/postgres/postgres.py +239 -0
  15. agno/db/postgres/schemas.py +19 -0
  16. agno/db/redis/redis.py +47 -0
  17. agno/db/singlestore/singlestore.py +47 -0
  18. agno/db/sqlite/async_sqlite.py +242 -0
  19. agno/db/sqlite/schemas.py +18 -0
  20. agno/db/sqlite/sqlite.py +239 -0
  21. agno/db/surrealdb/surrealdb.py +47 -0
  22. agno/knowledge/chunking/code.py +90 -0
  23. agno/knowledge/chunking/document.py +62 -2
  24. agno/knowledge/chunking/strategy.py +14 -0
  25. agno/knowledge/knowledge.py +7 -1
  26. agno/knowledge/reader/arxiv_reader.py +1 -0
  27. agno/knowledge/reader/csv_reader.py +1 -0
  28. agno/knowledge/reader/docx_reader.py +1 -0
  29. agno/knowledge/reader/firecrawl_reader.py +1 -0
  30. agno/knowledge/reader/json_reader.py +1 -0
  31. agno/knowledge/reader/markdown_reader.py +1 -0
  32. agno/knowledge/reader/pdf_reader.py +1 -0
  33. agno/knowledge/reader/pptx_reader.py +1 -0
  34. agno/knowledge/reader/s3_reader.py +1 -0
  35. agno/knowledge/reader/tavily_reader.py +1 -0
  36. agno/knowledge/reader/text_reader.py +1 -0
  37. agno/knowledge/reader/web_search_reader.py +1 -0
  38. agno/knowledge/reader/website_reader.py +1 -0
  39. agno/knowledge/reader/wikipedia_reader.py +1 -0
  40. agno/knowledge/reader/youtube_reader.py +1 -0
  41. agno/knowledge/utils.py +1 -0
  42. agno/learn/__init__.py +65 -0
  43. agno/learn/config.py +463 -0
  44. agno/learn/curate.py +185 -0
  45. agno/learn/machine.py +690 -0
  46. agno/learn/schemas.py +1043 -0
  47. agno/learn/stores/__init__.py +35 -0
  48. agno/learn/stores/entity_memory.py +3275 -0
  49. agno/learn/stores/learned_knowledge.py +1583 -0
  50. agno/learn/stores/protocol.py +117 -0
  51. agno/learn/stores/session_context.py +1217 -0
  52. agno/learn/stores/user_memory.py +1495 -0
  53. agno/learn/stores/user_profile.py +1220 -0
  54. agno/learn/utils.py +209 -0
  55. agno/models/base.py +59 -0
  56. agno/os/routers/agents/router.py +4 -4
  57. agno/os/routers/knowledge/knowledge.py +7 -0
  58. agno/os/routers/teams/router.py +3 -3
  59. agno/os/routers/workflows/router.py +5 -5
  60. agno/os/utils.py +55 -3
  61. agno/team/team.py +131 -0
  62. agno/tools/browserbase.py +78 -6
  63. agno/tools/google_bigquery.py +11 -2
  64. agno/utils/agent.py +30 -1
  65. agno/workflow/workflow.py +198 -0
  66. {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/METADATA +24 -2
  67. {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/RECORD +70 -56
  68. {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/WHEEL +0 -0
  69. {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/licenses/LICENSE +0 -0
  70. {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/top_level.txt +0 -0
agno/agent/agent.py CHANGED
@@ -46,6 +46,7 @@ from agno.filters import FilterExpr
46
46
  from agno.guardrails import BaseGuardrail
47
47
  from agno.knowledge.knowledge import Knowledge
48
48
  from agno.knowledge.types import KnowledgeFilter
49
+ from agno.learn.machine import LearningMachine
49
50
  from agno.media import Audio, File, Image, Video
50
51
  from agno.memory import MemoryManager
51
52
  from agno.models.base import Model
@@ -359,6 +360,12 @@ class Agent:
359
360
  # If True, resolve session_state, dependencies, and metadata in the user and system messages
360
361
  resolve_in_context: bool = True
361
362
 
363
+ # --- Learning Machine ---
364
+ # LearningMachine for unified learning capabilities
365
+ learning: Optional[Union[bool, LearningMachine]] = None
366
+ # Add learnings context to system prompt
367
+ add_learnings_to_context: bool = True
368
+
362
369
  # --- Extra Messages ---
363
370
  # A list of extra messages added after the system message and before the user message.
364
371
  # Use these for few-shot learning or to provide additional context to the Model.
@@ -527,6 +534,8 @@ class Agent:
527
534
  add_location_to_context: bool = False,
528
535
  timezone_identifier: Optional[str] = None,
529
536
  resolve_in_context: bool = True,
537
+ learning: Optional[Union[bool, LearningMachine]] = None,
538
+ add_learnings_to_context: bool = True,
530
539
  additional_input: Optional[List[Union[str, Dict, BaseModel, Message]]] = None,
531
540
  user_message_role: str = "user",
532
541
  build_user_context: bool = True,
@@ -656,6 +665,8 @@ class Agent:
656
665
  self.add_location_to_context = add_location_to_context
657
666
  self.timezone_identifier = timezone_identifier
658
667
  self.resolve_in_context = resolve_in_context
668
+ self.learning = learning
669
+ self.add_learnings_to_context = add_learnings_to_context
659
670
  self.additional_input = additional_input
660
671
  self.user_message_role = user_message_role
661
672
  self.build_user_context = build_user_context
@@ -705,6 +716,10 @@ class Agent:
705
716
  self.debug_level = debug_level
706
717
  self.telemetry = telemetry
707
718
 
719
+ # Internal use: _learning holds the resolved LearningMachine instance
720
+ # use get_learning_machine() to access it.
721
+ self._learning: Optional[LearningMachine] = None
722
+
708
723
  # If we are caching the agent session
709
724
  self._cached_session: Optional[AgentSession] = None
710
725
 
@@ -811,6 +826,49 @@ class Agent:
811
826
  self.enable_user_memories or self.enable_agentic_memory or self.memory_manager is not None
812
827
  )
813
828
 
829
+ def _set_learning_machine(self) -> None:
830
+ """Initialize LearningMachine with agent's db and model.
831
+
832
+ Sets the internal _learning field without modifying the public learning field.
833
+
834
+ Handles:
835
+ - learning=True: Create default LearningMachine
836
+ - learning=False/None: Disabled
837
+ - learning=LearningMachine(...): Use provided, inject db/model/knowledge
838
+ """
839
+ # Handle learning=False or learning=None
840
+ if self.learning is None or self.learning is False:
841
+ self._learning = None
842
+ return
843
+
844
+ # Check db requirement
845
+ if self.db is None:
846
+ log_warning("Database not provided. LearningMachine not initialized.")
847
+ self._learning = None
848
+ return
849
+
850
+ # Handle learning=True: create default LearningMachine
851
+ if self.learning is True:
852
+ self._learning = LearningMachine(db=self.db, model=self.model, user_profile=True)
853
+ return
854
+
855
+ # Handle learning=LearningMachine(...): inject dependencies
856
+ if isinstance(self.learning, LearningMachine):
857
+ if self.learning.db is None:
858
+ self.learning.db = self.db
859
+ if self.learning.model is None:
860
+ self.learning.model = self.model
861
+ self._learning = self.learning
862
+
863
+ def get_learning_machine(self) -> Optional[LearningMachine]:
864
+ """Get the resolved LearningMachine instance.
865
+
866
+ Returns:
867
+ The LearningMachine instance if learning is enabled and initialized,
868
+ None otherwise.
869
+ """
870
+ return self._learning
871
+
814
872
  def _set_session_summary_manager(self) -> None:
815
873
  if self.enable_session_summaries and self.session_summary_manager is None:
816
874
  self.session_summary_manager = SessionSummaryManager(model=self.model)
@@ -871,6 +929,8 @@ class Agent:
871
929
  self._set_session_summary_manager()
872
930
  if self.compress_tool_results or self.compression_manager is not None:
873
931
  self._set_compression_manager()
932
+ if self.learning is not None and self.learning is not False:
933
+ self._set_learning_machine()
874
934
 
875
935
  log_debug(f"Agent ID: {self.id}", center=True)
876
936
 
@@ -1008,6 +1068,7 @@ class Agent:
1008
1068
  13. Cleanup and store the run response and session
1009
1069
  """
1010
1070
  memory_future = None
1071
+ learning_future = None
1011
1072
  cultural_knowledge_future = None
1012
1073
 
1013
1074
  try:
@@ -1083,6 +1144,14 @@ class Agent:
1083
1144
  existing_future=memory_future,
1084
1145
  )
1085
1146
 
1147
+ # Start learning extraction as a background task (runs concurrently with the main execution)
1148
+ learning_future = self._start_learning_future(
1149
+ run_messages=run_messages,
1150
+ session=session,
1151
+ user_id=user_id,
1152
+ existing_future=learning_future,
1153
+ )
1154
+
1086
1155
  # Start cultural knowledge creation in background thread
1087
1156
  cultural_knowledge_future = self._start_cultural_knowledge_future(
1088
1157
  run_messages=run_messages,
@@ -1130,6 +1199,7 @@ class Agent:
1130
1199
  wait_for_open_threads(
1131
1200
  memory_future=memory_future, # type: ignore
1132
1201
  cultural_knowledge_future=cultural_knowledge_future, # type: ignore
1202
+ learning_future=learning_future, # type: ignore
1133
1203
  )
1134
1204
 
1135
1205
  return self._handle_agent_run_paused(
@@ -1164,6 +1234,7 @@ class Agent:
1164
1234
  wait_for_open_threads(
1165
1235
  memory_future=memory_future, # type: ignore
1166
1236
  cultural_knowledge_future=cultural_knowledge_future, # type: ignore
1237
+ learning_future=learning_future, # type: ignore
1167
1238
  )
1168
1239
 
1169
1240
  # 12. Create session summary
@@ -1251,6 +1322,8 @@ class Agent:
1251
1322
  memory_future.cancel()
1252
1323
  if cultural_knowledge_future is not None and not cultural_knowledge_future.done():
1253
1324
  cultural_knowledge_future.cancel()
1325
+ if learning_future is not None and not learning_future.done():
1326
+ learning_future.cancel()
1254
1327
 
1255
1328
  # Always disconnect connectable tools
1256
1329
  self._disconnect_connectable_tools()
@@ -1290,6 +1363,7 @@ class Agent:
1290
1363
  10. Cleanup and store the run response and session
1291
1364
  """
1292
1365
  memory_future = None
1366
+ learning_future = None
1293
1367
  cultural_knowledge_future = None
1294
1368
 
1295
1369
  try:
@@ -1366,6 +1440,14 @@ class Agent:
1366
1440
  existing_future=memory_future,
1367
1441
  )
1368
1442
 
1443
+ # Start learning extraction as a background task (runs concurrently with the main execution)
1444
+ learning_future = self._start_learning_future(
1445
+ run_messages=run_messages,
1446
+ session=session,
1447
+ user_id=user_id,
1448
+ existing_future=learning_future,
1449
+ )
1450
+
1369
1451
  # Start cultural knowledge creation in background thread
1370
1452
  cultural_knowledge_future = self._start_cultural_knowledge_future(
1371
1453
  run_messages=run_messages,
@@ -1454,6 +1536,7 @@ class Agent:
1454
1536
  yield from wait_for_thread_tasks_stream(
1455
1537
  memory_future=memory_future, # type: ignore
1456
1538
  cultural_knowledge_future=cultural_knowledge_future, # type: ignore
1539
+ learning_future=learning_future, # type: ignore
1457
1540
  stream_events=stream_events,
1458
1541
  run_response=run_response,
1459
1542
  events_to_skip=self.events_to_skip,
@@ -1493,6 +1576,7 @@ class Agent:
1493
1576
  yield from wait_for_thread_tasks_stream(
1494
1577
  memory_future=memory_future, # type: ignore
1495
1578
  cultural_knowledge_future=cultural_knowledge_future, # type: ignore
1579
+ learning_future=learning_future, # type: ignore
1496
1580
  stream_events=stream_events,
1497
1581
  run_response=run_response,
1498
1582
  )
@@ -1643,6 +1727,8 @@ class Agent:
1643
1727
  memory_future.cancel()
1644
1728
  if cultural_knowledge_future is not None and not cultural_knowledge_future.done():
1645
1729
  cultural_knowledge_future.cancel()
1730
+ if learning_future is not None and not learning_future.done():
1731
+ learning_future.cancel()
1646
1732
 
1647
1733
  # Always disconnect connectable tools
1648
1734
  self._disconnect_connectable_tools()
@@ -1964,8 +2050,9 @@ class Agent:
1964
2050
  await aregister_run(run_context.run_id)
1965
2051
  log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1966
2052
 
1967
- cultural_knowledge_task = None
1968
2053
  memory_task = None
2054
+ learning_task = None
2055
+ cultural_knowledge_task = None
1969
2056
 
1970
2057
  # Set up retry logic
1971
2058
  num_attempts = self.retries + 1
@@ -2063,6 +2150,14 @@ class Agent:
2063
2150
  existing_task=memory_task,
2064
2151
  )
2065
2152
 
2153
+ # Start learning extraction as a background task
2154
+ learning_task = await self._astart_learning_task(
2155
+ run_messages=run_messages,
2156
+ session=agent_session,
2157
+ user_id=user_id,
2158
+ existing_task=learning_task,
2159
+ )
2160
+
2066
2161
  # Start cultural knowledge creation as a background task (runs concurrently with the main execution)
2067
2162
  cultural_knowledge_task = await self._astart_cultural_knowledge_task(
2068
2163
  run_messages=run_messages,
@@ -2114,7 +2209,9 @@ class Agent:
2114
2209
  # We should break out of the run function
2115
2210
  if any(tool_call.is_paused for tool_call in run_response.tools or []):
2116
2211
  await await_for_open_threads(
2117
- memory_task=memory_task, cultural_knowledge_task=cultural_knowledge_task
2212
+ memory_task=memory_task,
2213
+ cultural_knowledge_task=cultural_knowledge_task,
2214
+ learning_task=learning_task,
2118
2215
  )
2119
2216
  return await self._ahandle_agent_run_paused(
2120
2217
  run_response=run_response, session=agent_session, user_id=user_id
@@ -2146,7 +2243,9 @@ class Agent:
2146
2243
 
2147
2244
  # 14. Wait for background memory creation
2148
2245
  await await_for_open_threads(
2149
- memory_task=memory_task, cultural_knowledge_task=cultural_knowledge_task
2246
+ memory_task=memory_task,
2247
+ cultural_knowledge_task=cultural_knowledge_task,
2248
+ learning_task=learning_task,
2150
2249
  )
2151
2250
 
2152
2251
  # 15. Create session summary
@@ -2262,6 +2361,12 @@ class Agent:
2262
2361
  await cultural_knowledge_task
2263
2362
  except asyncio.CancelledError:
2264
2363
  pass
2364
+ if learning_task is not None and not learning_task.done():
2365
+ learning_task.cancel()
2366
+ try:
2367
+ await learning_task
2368
+ except asyncio.CancelledError:
2369
+ pass
2265
2370
 
2266
2371
  # Always clean up the run tracking
2267
2372
  await acleanup_run(run_response.run_id) # type: ignore
@@ -2306,6 +2411,7 @@ class Agent:
2306
2411
 
2307
2412
  memory_task = None
2308
2413
  cultural_knowledge_task = None
2414
+ learning_task = None
2309
2415
 
2310
2416
  # 1. Read or create session. Reads from the database if provided.
2311
2417
  agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
@@ -2411,6 +2517,14 @@ class Agent:
2411
2517
  existing_task=memory_task,
2412
2518
  )
2413
2519
 
2520
+ # Start learning extraction as a background task
2521
+ learning_task = await self._astart_learning_task(
2522
+ run_messages=run_messages,
2523
+ session=agent_session,
2524
+ user_id=user_id,
2525
+ existing_task=learning_task,
2526
+ )
2527
+
2414
2528
  # Start cultural knowledge creation as a background task (runs concurrently with the main execution)
2415
2529
  cultural_knowledge_task = await self._astart_cultural_knowledge_task(
2416
2530
  run_messages=run_messages,
@@ -2503,6 +2617,7 @@ class Agent:
2503
2617
  async for item in await_for_thread_tasks_stream(
2504
2618
  memory_task=memory_task,
2505
2619
  cultural_knowledge_task=cultural_knowledge_task,
2620
+ learning_task=learning_task,
2506
2621
  stream_events=stream_events,
2507
2622
  run_response=run_response,
2508
2623
  ):
@@ -2533,6 +2648,7 @@ class Agent:
2533
2648
  async for item in await_for_thread_tasks_stream(
2534
2649
  memory_task=memory_task,
2535
2650
  cultural_knowledge_task=cultural_knowledge_task,
2651
+ learning_task=learning_task,
2536
2652
  stream_events=stream_events,
2537
2653
  run_response=run_response,
2538
2654
  events_to_skip=self.events_to_skip,
@@ -2728,6 +2844,13 @@ class Agent:
2728
2844
  except asyncio.CancelledError:
2729
2845
  pass
2730
2846
 
2847
+ if learning_task is not None and not learning_task.done():
2848
+ learning_task.cancel()
2849
+ try:
2850
+ await learning_task
2851
+ except asyncio.CancelledError:
2852
+ pass
2853
+
2731
2854
  # Always clean up the run tracking
2732
2855
  await acleanup_run(run_response.run_id) # type: ignore
2733
2856
 
@@ -6252,6 +6375,93 @@ class Agent:
6252
6375
 
6253
6376
  return None
6254
6377
 
6378
+ def _process_learnings(
6379
+ self,
6380
+ run_messages: RunMessages,
6381
+ session: AgentSession,
6382
+ user_id: Optional[str],
6383
+ ) -> None:
6384
+ """Process learnings from conversation (runs in background thread)."""
6385
+ if self._learning is None:
6386
+ return
6387
+
6388
+ try:
6389
+ # Convert run messages to list format expected by LearningMachine
6390
+ messages = run_messages.messages if run_messages else []
6391
+
6392
+ self._learning.process(
6393
+ messages=messages,
6394
+ user_id=user_id,
6395
+ session_id=session.session_id if session else None,
6396
+ agent_id=self.id,
6397
+ team_id=self.team_id,
6398
+ )
6399
+ log_debug("Learning extraction completed.")
6400
+ except Exception as e:
6401
+ log_warning(f"Error processing learnings: {e}")
6402
+
6403
+ async def _astart_learning_task(
6404
+ self,
6405
+ run_messages: RunMessages,
6406
+ session: AgentSession,
6407
+ user_id: Optional[str],
6408
+ existing_task: Optional[Task] = None,
6409
+ ) -> Optional[Task]:
6410
+ """Start learning extraction as async task.
6411
+
6412
+ Args:
6413
+ run_messages: The run messages containing conversation.
6414
+ session: The agent session.
6415
+ user_id: The user ID for learning extraction.
6416
+ existing_task: An existing task to cancel before starting a new one.
6417
+
6418
+ Returns:
6419
+ A new learning task if conditions are met, None otherwise.
6420
+ """
6421
+ # Cancel any existing task from a previous retry attempt
6422
+ if existing_task is not None and not existing_task.done():
6423
+ existing_task.cancel()
6424
+ try:
6425
+ await existing_task
6426
+ except CancelledError:
6427
+ pass
6428
+
6429
+ # Create new task if learning is enabled
6430
+ if self._learning is not None:
6431
+ log_debug("Starting learning extraction as async task.")
6432
+ return create_task(
6433
+ self._aprocess_learnings(
6434
+ run_messages=run_messages,
6435
+ session=session,
6436
+ user_id=user_id,
6437
+ )
6438
+ )
6439
+
6440
+ return None
6441
+
6442
+ async def _aprocess_learnings(
6443
+ self,
6444
+ run_messages: RunMessages,
6445
+ session: AgentSession,
6446
+ user_id: Optional[str],
6447
+ ) -> None:
6448
+ """Async process learnings from conversation."""
6449
+ if self._learning is None:
6450
+ return
6451
+
6452
+ try:
6453
+ messages = run_messages.messages if run_messages else []
6454
+ await self._learning.aprocess(
6455
+ messages=messages,
6456
+ user_id=user_id,
6457
+ session_id=session.session_id if session else None,
6458
+ agent_id=self.id,
6459
+ team_id=self.team_id,
6460
+ )
6461
+ log_debug("Learning extraction completed.")
6462
+ except Exception as e:
6463
+ log_warning(f"Error processing learnings: {e}")
6464
+
6255
6465
  def _start_memory_future(
6256
6466
  self,
6257
6467
  run_messages: RunMessages,
@@ -6285,6 +6495,40 @@ class Agent:
6285
6495
 
6286
6496
  return None
6287
6497
 
6498
+ def _start_learning_future(
6499
+ self,
6500
+ run_messages: RunMessages,
6501
+ session: AgentSession,
6502
+ user_id: Optional[str],
6503
+ existing_future: Optional[Future] = None,
6504
+ ) -> Optional[Future]:
6505
+ """Start learning extraction in background thread.
6506
+
6507
+ Args:
6508
+ run_messages: The run messages containing conversation.
6509
+ session: The agent session.
6510
+ user_id: The user ID for learning extraction.
6511
+ existing_future: An existing future to cancel before starting a new one.
6512
+
6513
+ Returns:
6514
+ A new learning future if conditions are met, None otherwise.
6515
+ """
6516
+ # Cancel any existing future from a previous retry attempt
6517
+ if existing_future is not None and not existing_future.done():
6518
+ existing_future.cancel()
6519
+
6520
+ # Create new future if learning is enabled
6521
+ if self._learning is not None:
6522
+ log_debug("Starting learning extraction in background thread.")
6523
+ return self.background_executor.submit(
6524
+ self._process_learnings,
6525
+ run_messages=run_messages,
6526
+ session=session,
6527
+ user_id=user_id,
6528
+ )
6529
+
6530
+ return None
6531
+
6288
6532
  def _start_cultural_knowledge_future(
6289
6533
  self,
6290
6534
  run_messages: RunMessages,
@@ -6376,6 +6620,15 @@ class Agent:
6376
6620
  if self.enable_agentic_memory:
6377
6621
  agent_tools.append(self._get_update_user_memory_function(user_id=user_id, async_mode=False))
6378
6622
 
6623
+ # Add learning machine tools
6624
+ if self._learning is not None:
6625
+ learning_tools = self._learning.get_tools(
6626
+ user_id=user_id,
6627
+ session_id=session.session_id if session else None,
6628
+ agent_id=self.id,
6629
+ )
6630
+ agent_tools.extend(learning_tools)
6631
+
6379
6632
  if self.enable_agentic_culture:
6380
6633
  agent_tools.append(self._get_update_cultural_knowledge_function(async_mode=False))
6381
6634
 
@@ -6485,6 +6738,18 @@ class Agent:
6485
6738
  if self.enable_agentic_memory:
6486
6739
  agent_tools.append(self._get_update_user_memory_function(user_id=user_id, async_mode=True))
6487
6740
 
6741
+ # Add learning machine tools (async)
6742
+ if self._learning is not None:
6743
+ learning_tools = await self._learning.aget_tools(
6744
+ user_id=user_id,
6745
+ session_id=session.session_id if session else None,
6746
+ agent_id=self.id,
6747
+ )
6748
+ agent_tools.extend(learning_tools)
6749
+
6750
+ if self.enable_agentic_culture:
6751
+ agent_tools.append(self._get_update_cultural_knowledge_function(async_mode=True))
6752
+
6488
6753
  if self.enable_agentic_state:
6489
6754
  agent_tools.append(Function(name="update_session_state", entrypoint=self._update_session_state_tool))
6490
6755
 
@@ -8097,12 +8362,22 @@ class Agent:
8097
8362
  "You should ALWAYS prefer information from this conversation over the past summary.\n\n"
8098
8363
  )
8099
8364
 
8100
- # 3.3.12 Add the system message from the Model
8365
+ # 3.3.12 then add learnings to the system prompt
8366
+ if self._learning is not None and self.add_learnings_to_context:
8367
+ learning_context = self._learning.build_context(
8368
+ user_id=user_id,
8369
+ session_id=session.session_id if session else None,
8370
+ agent_id=self.id,
8371
+ )
8372
+ if learning_context:
8373
+ system_message_content += learning_context + "\n"
8374
+
8375
+ # 3.3.13 Add the system message from the Model
8101
8376
  system_message_from_model = self.model.get_system_message_for_model(tools)
8102
8377
  if system_message_from_model is not None:
8103
8378
  system_message_content += system_message_from_model
8104
8379
 
8105
- # 3.3.13 Add the JSON output prompt if output_schema is provided and the model does not support native structured outputs or JSON schema outputs
8380
+ # 3.3.14 Add the JSON output prompt if output_schema is provided and the model does not support native structured outputs or JSON schema outputs
8106
8381
  # or if use_json_mode is True
8107
8382
  if (
8108
8383
  output_schema is not None
@@ -8114,11 +8389,11 @@ class Agent:
8114
8389
  ):
8115
8390
  system_message_content += f"{get_json_output_prompt(output_schema)}" # type: ignore
8116
8391
 
8117
- # 3.3.14 Add the response model format prompt if output_schema is provided (Pydantic only)
8392
+ # 3.3.15 Add the response model format prompt if output_schema is provided (Pydantic only)
8118
8393
  if output_schema is not None and self.parser_model is not None and not isinstance(output_schema, dict):
8119
8394
  system_message_content += f"{get_response_model_format_prompt(output_schema)}"
8120
8395
 
8121
- # 3.3.15 Add the session state to the system message
8396
+ # 3.3.16 Add the session state to the system message
8122
8397
  if add_session_state_to_context and session_state is not None:
8123
8398
  system_message_content += f"\n<session_state>\n{session_state}\n</session_state>\n\n"
8124
8399
 
@@ -8449,12 +8724,22 @@ class Agent:
8449
8724
  "You should ALWAYS prefer information from this conversation over the past summary.\n\n"
8450
8725
  )
8451
8726
 
8452
- # 3.3.12 Add the system message from the Model
8727
+ # 3.3.12 then add learnings to the system prompt
8728
+ if self._learning is not None and self.add_learnings_to_context:
8729
+ learning_context = await self._learning.abuild_context(
8730
+ user_id=user_id,
8731
+ session_id=session.session_id if session else None,
8732
+ agent_id=self.id,
8733
+ )
8734
+ if learning_context:
8735
+ system_message_content += learning_context + "\n"
8736
+
8737
+ # 3.3.13 Add the system message from the Model
8453
8738
  system_message_from_model = self.model.get_system_message_for_model(tools)
8454
8739
  if system_message_from_model is not None:
8455
8740
  system_message_content += system_message_from_model
8456
8741
 
8457
- # 3.3.13 Add the JSON output prompt if output_schema is provided and the model does not support native structured outputs or JSON schema outputs
8742
+ # 3.3.14 Add the JSON output prompt if output_schema is provided and the model does not support native structured outputs or JSON schema outputs
8458
8743
  # or if use_json_mode is True
8459
8744
  if (
8460
8745
  output_schema is not None
@@ -8466,11 +8751,11 @@ class Agent:
8466
8751
  ):
8467
8752
  system_message_content += f"{get_json_output_prompt(output_schema)}" # type: ignore
8468
8753
 
8469
- # 3.3.14 Add the response model format prompt if output_schema is provided (Pydantic only)
8754
+ # 3.3.15 Add the response model format prompt if output_schema is provided (Pydantic only)
8470
8755
  if output_schema is not None and self.parser_model is not None and not isinstance(output_schema, dict):
8471
8756
  system_message_content += f"{get_response_model_format_prompt(output_schema)}"
8472
8757
 
8473
- # 3.3.15 Add the session state to the system message
8758
+ # 3.3.16 Add the session state to the system message
8474
8759
  if add_session_state_to_context and session_state is not None:
8475
8760
  system_message_content += self._get_formatted_session_state_for_system_message(session_state)
8476
8761
 
@@ -9592,18 +9877,30 @@ class Agent:
9592
9877
  fields_for_new_agent: Dict[str, Any] = {}
9593
9878
 
9594
9879
  for f in fields(self):
9880
+ # Skip private fields (not part of __init__ signature)
9881
+ if f.name.startswith("_"):
9882
+ continue
9883
+
9595
9884
  field_value = getattr(self, f.name)
9596
9885
  if field_value is not None:
9597
- fields_for_new_agent[f.name] = self._deep_copy_field(f.name, field_value)
9886
+ try:
9887
+ fields_for_new_agent[f.name] = self._deep_copy_field(f.name, field_value)
9888
+ except Exception as e:
9889
+ log_warning(f"Failed to deep copy field '{f.name}': {e}. Using original value.")
9890
+ fields_for_new_agent[f.name] = field_value
9598
9891
 
9599
9892
  # Update fields if provided
9600
9893
  if update:
9601
9894
  fields_for_new_agent.update(update)
9602
9895
 
9603
9896
  # Create a new Agent
9604
- new_agent = self.__class__(**fields_for_new_agent)
9605
- log_debug(f"Created new {self.__class__.__name__}")
9606
- return new_agent
9897
+ try:
9898
+ new_agent = self.__class__(**fields_for_new_agent)
9899
+ log_debug(f"Created new {self.__class__.__name__}")
9900
+ return new_agent
9901
+ except Exception as e:
9902
+ log_error(f"Failed to create deep copy of {self.__class__.__name__}: {e}")
9903
+ raise
9607
9904
 
9608
9905
  def _deep_copy_field(self, field_name: str, field_value: Any) -> Any:
9609
9906
  """Helper method to deep copy a field based on its type."""
@@ -9613,19 +9910,52 @@ class Agent:
9613
9910
  if field_name == "reasoning_agent":
9614
9911
  return field_value.deep_copy()
9615
9912
 
9616
- # For storage, model and reasoning_model, use a deep copy
9617
- elif field_name in ("db", "model", "reasoning_model"):
9913
+ # For tools, share MCP tools but copy others
9914
+ if field_name == "tools" and field_value is not None:
9618
9915
  try:
9619
- return deepcopy(field_value)
9620
- except Exception:
9621
- try:
9622
- return copy(field_value)
9623
- except Exception as e:
9624
- log_warning(f"Failed to copy field: {field_name} - {e}")
9625
- return field_value
9916
+ copied_tools = []
9917
+ for tool in field_value:
9918
+ try:
9919
+ # Share MCP tools (they maintain server connections)
9920
+ is_mcp_tool = hasattr(type(tool), "__mro__") and any(
9921
+ c.__name__ in ["MCPTools", "MultiMCPTools"] for c in type(tool).__mro__
9922
+ )
9923
+ if is_mcp_tool:
9924
+ copied_tools.append(tool)
9925
+ else:
9926
+ try:
9927
+ copied_tools.append(deepcopy(tool))
9928
+ except Exception:
9929
+ # Tool can't be deep copied, share by reference
9930
+ copied_tools.append(tool)
9931
+ except Exception:
9932
+ # MCP detection failed, share tool by reference to be safe
9933
+ copied_tools.append(tool)
9934
+ return copied_tools
9935
+ except Exception as e:
9936
+ # If entire tools processing fails, log and return original list
9937
+ log_warning(f"Failed to process tools for deep copy: {e}")
9938
+ return field_value
9939
+
9940
+ # Share heavy resources - these maintain connections/pools that shouldn't be duplicated
9941
+ if field_name in (
9942
+ "db",
9943
+ "model",
9944
+ "reasoning_model",
9945
+ "knowledge",
9946
+ "memory_manager",
9947
+ "parser_model",
9948
+ "output_model",
9949
+ "session_summary_manager",
9950
+ "culture_manager",
9951
+ "compression_manager",
9952
+ "learning",
9953
+ "skills",
9954
+ ):
9955
+ return field_value
9626
9956
 
9627
9957
  # For compound types, attempt a deep copy
9628
- elif isinstance(field_value, (list, dict, set)):
9958
+ if isinstance(field_value, (list, dict, set)):
9629
9959
  try:
9630
9960
  return deepcopy(field_value)
9631
9961
  except Exception:
@@ -9636,7 +9966,7 @@ class Agent:
9636
9966
  return field_value
9637
9967
 
9638
9968
  # For pydantic models, attempt a model_copy
9639
- elif isinstance(field_value, BaseModel):
9969
+ if isinstance(field_value, BaseModel):
9640
9970
  try:
9641
9971
  return field_value.model_copy(deep=True)
9642
9972
  except Exception:
@@ -9648,8 +9978,6 @@ class Agent:
9648
9978
 
9649
9979
  # For other types, attempt a shallow copy first
9650
9980
  try:
9651
- from copy import copy
9652
-
9653
9981
  return copy(field_value)
9654
9982
  except Exception:
9655
9983
  # If copy fails, return as is
@@ -11291,6 +11619,7 @@ class Agent:
11291
11619
  "has_memory": self.enable_user_memories is True
11292
11620
  or self.enable_agentic_memory is True
11293
11621
  or self.memory_manager is not None,
11622
+ "has_learnings": self._learning is not None,
11294
11623
  "has_culture": self.enable_agentic_culture is True
11295
11624
  or self.update_cultural_knowledge is True
11296
11625
  or self.culture_manager is not None,