agno 2.3.19__py3-none-any.whl → 2.3.21__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 (39) hide show
  1. agno/agent/agent.py +2466 -2048
  2. agno/db/dynamo/utils.py +26 -3
  3. agno/db/firestore/utils.py +25 -10
  4. agno/db/gcs_json/utils.py +14 -2
  5. agno/db/in_memory/utils.py +14 -2
  6. agno/db/json/utils.py +14 -2
  7. agno/db/mysql/utils.py +13 -3
  8. agno/db/postgres/utils.py +13 -3
  9. agno/db/redis/utils.py +26 -10
  10. agno/db/schemas/memory.py +15 -19
  11. agno/db/singlestore/utils.py +13 -3
  12. agno/db/sqlite/utils.py +15 -3
  13. agno/db/utils.py +22 -0
  14. agno/eval/agent_as_judge.py +24 -14
  15. agno/knowledge/embedder/mistral.py +1 -1
  16. agno/models/litellm/chat.py +6 -0
  17. agno/os/routers/evals/evals.py +0 -9
  18. agno/os/routers/evals/utils.py +6 -6
  19. agno/os/routers/knowledge/schemas.py +1 -1
  20. agno/os/routers/memory/schemas.py +14 -1
  21. agno/os/routers/metrics/schemas.py +1 -1
  22. agno/os/schema.py +11 -9
  23. agno/run/__init__.py +2 -4
  24. agno/run/agent.py +19 -19
  25. agno/run/cancel.py +65 -52
  26. agno/run/cancellation_management/__init__.py +9 -0
  27. agno/run/cancellation_management/base.py +78 -0
  28. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  29. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  30. agno/run/team.py +19 -19
  31. agno/team/team.py +1217 -1136
  32. agno/utils/response.py +1 -13
  33. agno/vectordb/weaviate/__init__.py +1 -1
  34. agno/workflow/workflow.py +23 -16
  35. {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/METADATA +60 -129
  36. {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/RECORD +39 -35
  37. {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/WHEEL +0 -0
  38. {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/licenses/LICENSE +0 -0
  39. {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/top_level.txt +0 -0
agno/team/team.py CHANGED
@@ -6,6 +6,7 @@ import json
6
6
  import time
7
7
  import warnings
8
8
  from collections import ChainMap, deque
9
+ from concurrent.futures import Future
9
10
  from copy import copy
10
11
  from dataclasses import dataclass
11
12
  from os import getenv
@@ -56,13 +57,16 @@ from agno.reasoning.step import NextAction, ReasoningStep, ReasoningSteps
56
57
  from agno.run import RunContext, RunStatus
57
58
  from agno.run.agent import RunEvent, RunOutput, RunOutputEvent
58
59
  from agno.run.cancel import (
59
- cancel_run as cancel_run_global,
60
- )
61
- from agno.run.cancel import (
60
+ acleanup_run,
61
+ araise_if_cancelled,
62
+ aregister_run,
62
63
  cleanup_run,
63
64
  raise_if_cancelled,
64
65
  register_run,
65
66
  )
67
+ from agno.run.cancel import (
68
+ cancel_run as cancel_run_global,
69
+ )
66
70
  from agno.run.messages import RunMessages
67
71
  from agno.run.team import (
68
72
  TeamRunEvent,
@@ -169,7 +173,6 @@ from agno.utils.reasoning import (
169
173
  )
170
174
  from agno.utils.response import (
171
175
  check_if_run_cancelled,
172
- generator_wrapper,
173
176
  )
174
177
  from agno.utils.safe_formatter import SafeFormatter
175
178
  from agno.utils.string import generate_id_from_name, parse_response_dict_str, parse_response_model_str
@@ -1040,7 +1043,7 @@ class Team:
1040
1043
  for tool in self.tools:
1041
1044
  if (
1042
1045
  hasattr(tool, "requires_connect")
1043
- and tool.requires_connect
1046
+ and tool.requires_connect # type: ignore
1044
1047
  and hasattr(tool, "connect")
1045
1048
  and tool not in self._connectable_tools_initialized_on_run
1046
1049
  ):
@@ -1456,175 +1459,231 @@ class Team:
1456
1459
  12. Create session summary
1457
1460
  13. Cleanup and store (scrub, stop timer, add to session, calculate metrics, save session)
1458
1461
  """
1459
- # 1. Execute pre-hooks
1460
- run_input = cast(TeamRunInput, run_response.input)
1461
- self.model = cast(Model, self.model)
1462
- if self.pre_hooks is not None:
1463
- # Can modify the run input
1464
- pre_hook_iterator = self._execute_pre_hooks(
1465
- hooks=self.pre_hooks, # type: ignore
1466
- run_response=run_response,
1467
- run_input=run_input,
1468
- run_context=run_context,
1469
- session=session,
1470
- user_id=user_id,
1471
- debug_mode=debug_mode,
1472
- background_tasks=background_tasks,
1473
- **kwargs,
1474
- )
1475
- # Consume the generator without yielding
1476
- deque(pre_hook_iterator, maxlen=0)
1462
+ log_debug(f"Team Run Start: {run_response.run_id}", center=True)
1477
1463
 
1478
- # 2. Determine tools for model
1479
- # Initialize team run context
1480
- team_run_context: Dict[str, Any] = {}
1464
+ memory_future = None
1465
+ try:
1466
+ # Set up retry logic
1467
+ num_attempts = self.retries + 1
1468
+ for attempt in range(num_attempts):
1469
+ try:
1470
+ # 1. Execute pre-hooks
1471
+ run_input = cast(TeamRunInput, run_response.input)
1472
+ self.model = cast(Model, self.model)
1473
+ if self.pre_hooks is not None:
1474
+ # Can modify the run input
1475
+ pre_hook_iterator = self._execute_pre_hooks(
1476
+ hooks=self.pre_hooks, # type: ignore
1477
+ run_response=run_response,
1478
+ run_input=run_input,
1479
+ run_context=run_context,
1480
+ session=session,
1481
+ user_id=user_id,
1482
+ debug_mode=debug_mode,
1483
+ background_tasks=background_tasks,
1484
+ **kwargs,
1485
+ )
1486
+ # Consume the generator without yielding
1487
+ deque(pre_hook_iterator, maxlen=0)
1481
1488
 
1482
- _tools = self._determine_tools_for_model(
1483
- model=self.model,
1484
- run_response=run_response,
1485
- run_context=run_context,
1486
- team_run_context=team_run_context,
1487
- session=session,
1488
- user_id=user_id,
1489
- async_mode=False,
1490
- input_message=run_input.input_content,
1491
- images=run_input.images,
1492
- videos=run_input.videos,
1493
- audio=run_input.audios,
1494
- files=run_input.files,
1495
- debug_mode=debug_mode,
1496
- add_history_to_context=add_history_to_context,
1497
- add_session_state_to_context=add_session_state_to_context,
1498
- add_dependencies_to_context=add_dependencies_to_context,
1499
- stream=False,
1500
- stream_events=False,
1501
- )
1489
+ # 2. Determine tools for model
1490
+ # Initialize team run context
1491
+ team_run_context: Dict[str, Any] = {}
1502
1492
 
1503
- # 3. Prepare run messages
1504
- run_messages: RunMessages = self._get_run_messages(
1505
- run_response=run_response,
1506
- session=session,
1507
- run_context=run_context,
1508
- user_id=user_id,
1509
- input_message=run_input.input_content,
1510
- audio=run_input.audios,
1511
- images=run_input.images,
1512
- videos=run_input.videos,
1513
- files=run_input.files,
1514
- add_history_to_context=add_history_to_context,
1515
- add_dependencies_to_context=add_dependencies_to_context,
1516
- add_session_state_to_context=add_session_state_to_context,
1517
- tools=_tools,
1518
- **kwargs,
1519
- )
1520
- if len(run_messages.messages) == 0:
1521
- log_error("No messages to be sent to the model.")
1493
+ _tools = self._determine_tools_for_model(
1494
+ model=self.model,
1495
+ run_response=run_response,
1496
+ run_context=run_context,
1497
+ team_run_context=team_run_context,
1498
+ session=session,
1499
+ user_id=user_id,
1500
+ async_mode=False,
1501
+ input_message=run_input.input_content,
1502
+ images=run_input.images,
1503
+ videos=run_input.videos,
1504
+ audio=run_input.audios,
1505
+ files=run_input.files,
1506
+ debug_mode=debug_mode,
1507
+ add_history_to_context=add_history_to_context,
1508
+ add_session_state_to_context=add_session_state_to_context,
1509
+ add_dependencies_to_context=add_dependencies_to_context,
1510
+ stream=False,
1511
+ stream_events=False,
1512
+ )
1522
1513
 
1523
- log_debug(f"Team Run Start: {run_response.run_id}", center=True)
1514
+ # 3. Prepare run messages
1515
+ run_messages: RunMessages = self._get_run_messages(
1516
+ run_response=run_response,
1517
+ session=session,
1518
+ run_context=run_context,
1519
+ user_id=user_id,
1520
+ input_message=run_input.input_content,
1521
+ audio=run_input.audios,
1522
+ images=run_input.images,
1523
+ videos=run_input.videos,
1524
+ files=run_input.files,
1525
+ add_history_to_context=add_history_to_context,
1526
+ add_dependencies_to_context=add_dependencies_to_context,
1527
+ add_session_state_to_context=add_session_state_to_context,
1528
+ tools=_tools,
1529
+ **kwargs,
1530
+ )
1531
+ if len(run_messages.messages) == 0:
1532
+ log_error("No messages to be sent to the model.")
1524
1533
 
1525
- # 4. Start memory creation in background thread
1526
- memory_future = None
1527
- if (
1528
- run_messages.user_message is not None
1529
- and self.memory_manager is not None
1530
- and self.enable_user_memories
1531
- and not self.enable_agentic_memory
1532
- ):
1533
- log_debug("Starting memory creation in background thread.")
1534
- memory_future = self.background_executor.submit(
1535
- self._make_memories, run_messages=run_messages, user_id=user_id
1536
- )
1534
+ # 4. Start memory creation in background thread
1535
+ memory_future = self._start_memory_future(
1536
+ run_messages=run_messages,
1537
+ user_id=user_id,
1538
+ existing_future=None,
1539
+ )
1537
1540
 
1538
- raise_if_cancelled(run_response.run_id) # type: ignore
1541
+ raise_if_cancelled(run_response.run_id) # type: ignore
1539
1542
 
1540
- # 5. Reason about the task if reasoning is enabled
1541
- self._handle_reasoning(run_response=run_response, run_messages=run_messages)
1543
+ # 5. Reason about the task if reasoning is enabled
1544
+ self._handle_reasoning(run_response=run_response, run_messages=run_messages)
1542
1545
 
1543
- # Check for cancellation before model call
1544
- raise_if_cancelled(run_response.run_id) # type: ignore
1546
+ # Check for cancellation before model call
1547
+ raise_if_cancelled(run_response.run_id) # type: ignore
1545
1548
 
1546
- # 6. Get the model response for the team leader
1547
- self.model = cast(Model, self.model)
1548
- model_response: ModelResponse = self.model.response(
1549
- messages=run_messages.messages,
1550
- response_format=response_format,
1551
- tools=_tools,
1552
- tool_choice=self.tool_choice,
1553
- tool_call_limit=self.tool_call_limit,
1554
- send_media_to_model=self.send_media_to_model,
1555
- compression_manager=self.compression_manager if self.compress_tool_results else None,
1556
- )
1549
+ # 6. Get the model response for the team leader
1550
+ self.model = cast(Model, self.model)
1551
+ model_response: ModelResponse = self.model.response(
1552
+ messages=run_messages.messages,
1553
+ response_format=response_format,
1554
+ tools=_tools,
1555
+ tool_choice=self.tool_choice,
1556
+ tool_call_limit=self.tool_call_limit,
1557
+ send_media_to_model=self.send_media_to_model,
1558
+ compression_manager=self.compression_manager if self.compress_tool_results else None,
1559
+ )
1557
1560
 
1558
- # Check for cancellation after model call
1559
- raise_if_cancelled(run_response.run_id) # type: ignore
1561
+ # Check for cancellation after model call
1562
+ raise_if_cancelled(run_response.run_id) # type: ignore
1560
1563
 
1561
- # If an output model is provided, generate output using the output model
1562
- self._parse_response_with_output_model(model_response, run_messages)
1564
+ # If an output model is provided, generate output using the output model
1565
+ self._parse_response_with_output_model(model_response, run_messages)
1563
1566
 
1564
- # If a parser model is provided, structure the response separately
1565
- self._parse_response_with_parser_model(model_response, run_messages, run_context=run_context)
1567
+ # If a parser model is provided, structure the response separately
1568
+ self._parse_response_with_parser_model(model_response, run_messages, run_context=run_context)
1566
1569
 
1567
- # 7. Update TeamRunOutput with the model response
1568
- self._update_run_response(
1569
- model_response=model_response,
1570
- run_response=run_response,
1571
- run_messages=run_messages,
1572
- run_context=run_context,
1573
- )
1570
+ # 7. Update TeamRunOutput with the model response
1571
+ self._update_run_response(
1572
+ model_response=model_response,
1573
+ run_response=run_response,
1574
+ run_messages=run_messages,
1575
+ run_context=run_context,
1576
+ )
1574
1577
 
1575
- # 8. Store media if enabled
1576
- if self.store_media:
1577
- store_media_util(run_response, model_response)
1578
+ # 8. Store media if enabled
1579
+ if self.store_media:
1580
+ store_media_util(run_response, model_response)
1578
1581
 
1579
- # 9. Convert response to structured format
1580
- self._convert_response_to_structured_format(run_response=run_response, run_context=run_context)
1582
+ # 9. Convert response to structured format
1583
+ self._convert_response_to_structured_format(run_response=run_response, run_context=run_context)
1581
1584
 
1582
- # 10. Execute post-hooks after output is generated but before response is returned
1583
- if self.post_hooks is not None:
1584
- iterator = self._execute_post_hooks(
1585
- hooks=self.post_hooks, # type: ignore
1586
- run_output=run_response,
1587
- run_context=run_context,
1588
- session=session,
1589
- user_id=user_id,
1590
- debug_mode=debug_mode,
1591
- background_tasks=background_tasks,
1592
- **kwargs,
1593
- )
1594
- deque(iterator, maxlen=0)
1595
- raise_if_cancelled(run_response.run_id) # type: ignore
1585
+ # 10. Execute post-hooks after output is generated but before response is returned
1586
+ if self.post_hooks is not None:
1587
+ iterator = self._execute_post_hooks(
1588
+ hooks=self.post_hooks, # type: ignore
1589
+ run_output=run_response,
1590
+ run_context=run_context,
1591
+ session=session,
1592
+ user_id=user_id,
1593
+ debug_mode=debug_mode,
1594
+ background_tasks=background_tasks,
1595
+ **kwargs,
1596
+ )
1597
+ deque(iterator, maxlen=0)
1598
+ raise_if_cancelled(run_response.run_id) # type: ignore
1596
1599
 
1597
- # 11. Wait for background memory creation
1598
- wait_for_open_threads(memory_future=memory_future)
1600
+ # 11. Wait for background memory creation
1601
+ wait_for_open_threads(memory_future=memory_future) # type: ignore
1599
1602
 
1600
- raise_if_cancelled(run_response.run_id) # type: ignore
1603
+ raise_if_cancelled(run_response.run_id) # type: ignore
1601
1604
 
1602
- # 12. Create session summary
1603
- if self.session_summary_manager is not None:
1604
- # Upsert the RunOutput to Team Session before creating the session summary
1605
- session.upsert_run(run_response=run_response)
1606
- try:
1607
- self.session_summary_manager.create_session_summary(session=session)
1608
- except Exception as e:
1609
- log_warning(f"Error in session summary creation: {str(e)}")
1605
+ # 12. Create session summary
1606
+ if self.session_summary_manager is not None:
1607
+ # Upsert the RunOutput to Team Session before creating the session summary
1608
+ session.upsert_run(run_response=run_response)
1609
+ try:
1610
+ self.session_summary_manager.create_session_summary(session=session)
1611
+ except Exception as e:
1612
+ log_warning(f"Error in session summary creation: {str(e)}")
1610
1613
 
1611
- raise_if_cancelled(run_response.run_id) # type: ignore
1614
+ raise_if_cancelled(run_response.run_id) # type: ignore
1615
+
1616
+ # Set the run status to completed
1617
+ run_response.status = RunStatus.completed
1618
+
1619
+ # 13. Cleanup and store the run response
1620
+ self._cleanup_and_store(run_response=run_response, session=session)
1621
+
1622
+ # Log Team Telemetry
1623
+ self._log_team_telemetry(session_id=session.session_id, run_id=run_response.run_id)
1624
+
1625
+ log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
1626
+
1627
+ return run_response
1628
+ except RunCancelledException as e:
1629
+ # Handle run cancellation during streaming
1630
+ log_info(f"Team run {run_response.run_id} was cancelled during streaming")
1631
+ run_response.status = RunStatus.cancelled
1632
+ run_response.content = str(e)
1633
+
1634
+ # Cleanup and store the run response and session
1635
+ self._cleanup_and_store(run_response=run_response, session=session)
1636
+
1637
+ return run_response
1638
+ except (InputCheckError, OutputCheckError) as e:
1639
+ run_response.status = RunStatus.error
1640
+
1641
+ if run_response.content is None:
1642
+ run_response.content = str(e)
1643
+
1644
+ log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
1645
+
1646
+ self._cleanup_and_store(run_response=run_response, session=session)
1647
+
1648
+ return run_response
1649
+ except KeyboardInterrupt:
1650
+ run_response = cast(TeamRunOutput, run_response)
1651
+ run_response.status = RunStatus.cancelled
1652
+ run_response.content = "Operation cancelled by user"
1653
+ return run_response
1654
+ except Exception as e:
1655
+ if attempt < num_attempts - 1:
1656
+ # Calculate delay with exponential backoff if enabled
1657
+ if self.exponential_backoff:
1658
+ delay = self.delay_between_retries * (2**attempt)
1659
+ else:
1660
+ delay = self.delay_between_retries
1612
1661
 
1613
- # Set the run status to completed
1614
- run_response.status = RunStatus.completed
1662
+ log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
1663
+ time.sleep(delay)
1664
+ continue
1615
1665
 
1616
- # 13. Cleanup and store the run response
1617
- self._cleanup_and_store(run_response=run_response, session=session)
1666
+ run_response.status = RunStatus.error
1618
1667
 
1619
- # Log Team Telemetry
1620
- self._log_team_telemetry(session_id=session.session_id, run_id=run_response.run_id)
1668
+ # If the content is None, set it to the error message
1669
+ if run_response.content is None:
1670
+ run_response.content = str(e)
1621
1671
 
1622
- log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
1672
+ log_error(f"Error in Agent run: {str(e)}")
1623
1673
 
1624
- # Disconnect tools and clean up run tracking
1625
- self._disconnect_connectable_tools()
1626
- cleanup_run(run_response.run_id) # type: ignore
1674
+ # Cleanup and store the run response and session
1675
+ self._cleanup_and_store(run_response=run_response, session=session)
1627
1676
 
1677
+ return run_response
1678
+ finally:
1679
+ # Cancel background futures on error (wait_for_open_threads handles waiting on success)
1680
+ if memory_future is not None and not memory_future.done():
1681
+ memory_future.cancel()
1682
+
1683
+ # Always disconnect connectable tools
1684
+ self._disconnect_connectable_tools()
1685
+ # Always clean up the run tracking
1686
+ cleanup_run(run_response.run_id) # type: ignore
1628
1687
  return run_response
1629
1688
 
1630
1689
  def _run_stream(
@@ -1656,252 +1715,328 @@ class Team:
1656
1715
  9. Create session summary
1657
1716
  10. Cleanup and store (scrub, add to session, calculate metrics, save session)
1658
1717
  """
1659
-
1660
- # 1. Execute pre-hooks
1661
- run_input = cast(TeamRunInput, run_response.input)
1662
- self.model = cast(Model, self.model)
1663
- if self.pre_hooks is not None:
1664
- # Can modify the run input
1665
- pre_hook_iterator = self._execute_pre_hooks(
1666
- hooks=self.pre_hooks, # type: ignore
1667
- run_response=run_response,
1668
- run_context=run_context,
1669
- run_input=run_input,
1670
- session=session,
1671
- user_id=user_id,
1672
- debug_mode=debug_mode,
1673
- stream_events=stream_events,
1674
- background_tasks=background_tasks,
1675
- **kwargs,
1676
- )
1677
- for pre_hook_event in pre_hook_iterator:
1678
- yield pre_hook_event
1679
-
1680
- # 2. Determine tools for model
1681
- # Initialize team run context
1682
- team_run_context: Dict[str, Any] = {}
1683
-
1684
- _tools = self._determine_tools_for_model(
1685
- model=self.model,
1686
- run_response=run_response,
1687
- run_context=run_context,
1688
- team_run_context=team_run_context,
1689
- session=session,
1690
- user_id=user_id,
1691
- async_mode=False,
1692
- input_message=run_input.input_content,
1693
- images=run_input.images,
1694
- videos=run_input.videos,
1695
- audio=run_input.audios,
1696
- files=run_input.files,
1697
- debug_mode=debug_mode,
1698
- add_history_to_context=add_history_to_context,
1699
- add_session_state_to_context=add_session_state_to_context,
1700
- add_dependencies_to_context=add_dependencies_to_context,
1701
- stream=True,
1702
- stream_events=stream_events,
1703
- )
1704
-
1705
- # 3. Prepare run messages
1706
- run_messages: RunMessages = self._get_run_messages(
1707
- run_response=run_response,
1708
- run_context=run_context,
1709
- session=session,
1710
- user_id=user_id,
1711
- input_message=run_input.input_content,
1712
- audio=run_input.audios,
1713
- images=run_input.images,
1714
- videos=run_input.videos,
1715
- files=run_input.files,
1716
- add_history_to_context=add_history_to_context,
1717
- add_dependencies_to_context=add_dependencies_to_context,
1718
- add_session_state_to_context=add_session_state_to_context,
1719
- tools=_tools,
1720
- **kwargs,
1721
- )
1722
- if len(run_messages.messages) == 0:
1723
- log_error("No messages to be sent to the model.")
1724
-
1725
1718
  log_debug(f"Team Run Start: {run_response.run_id}", center=True)
1726
1719
 
1727
- # 4. Start memory creation in background thread
1728
1720
  memory_future = None
1729
- if (
1730
- run_messages.user_message is not None
1731
- and self.memory_manager is not None
1732
- and self.enable_user_memories
1733
- and not self.enable_agentic_memory
1734
- ):
1735
- log_debug("Starting memory creation in background thread.")
1736
- memory_future = self.background_executor.submit(
1737
- self._make_memories, run_messages=run_messages, user_id=user_id
1738
- )
1721
+ try:
1722
+ # Set up retry logic
1723
+ num_attempts = self.retries + 1
1724
+ for attempt in range(num_attempts):
1725
+ if num_attempts > 1:
1726
+ log_debug(f"Retrying Team run {run_response.run_id}. Attempt {attempt + 1} of {num_attempts}...")
1739
1727
 
1740
- # Start the Run by yielding a RunStarted event
1741
- if stream_events:
1742
- yield handle_event( # type: ignore
1743
- create_team_run_started_event(run_response),
1744
- run_response,
1745
- events_to_skip=self.events_to_skip,
1746
- store_events=self.store_events,
1747
- )
1728
+ try:
1729
+ # 1. Execute pre-hooks
1730
+ run_input = cast(TeamRunInput, run_response.input)
1731
+ self.model = cast(Model, self.model)
1732
+ if self.pre_hooks is not None:
1733
+ # Can modify the run input
1734
+ pre_hook_iterator = self._execute_pre_hooks(
1735
+ hooks=self.pre_hooks, # type: ignore
1736
+ run_response=run_response,
1737
+ run_context=run_context,
1738
+ run_input=run_input,
1739
+ session=session,
1740
+ user_id=user_id,
1741
+ debug_mode=debug_mode,
1742
+ stream_events=stream_events,
1743
+ background_tasks=background_tasks,
1744
+ **kwargs,
1745
+ )
1746
+ for pre_hook_event in pre_hook_iterator:
1747
+ yield pre_hook_event
1748
1748
 
1749
- raise_if_cancelled(run_response.run_id) # type: ignore
1749
+ # 2. Determine tools for model
1750
+ # Initialize team run context
1751
+ team_run_context: Dict[str, Any] = {}
1750
1752
 
1751
- # 5. Reason about the task if reasoning is enabled
1752
- yield from self._handle_reasoning_stream(
1753
- run_response=run_response,
1754
- run_messages=run_messages,
1755
- stream_events=stream_events,
1756
- )
1753
+ _tools = self._determine_tools_for_model(
1754
+ model=self.model,
1755
+ run_response=run_response,
1756
+ run_context=run_context,
1757
+ team_run_context=team_run_context,
1758
+ session=session,
1759
+ user_id=user_id,
1760
+ async_mode=False,
1761
+ input_message=run_input.input_content,
1762
+ images=run_input.images,
1763
+ videos=run_input.videos,
1764
+ audio=run_input.audios,
1765
+ files=run_input.files,
1766
+ debug_mode=debug_mode,
1767
+ add_history_to_context=add_history_to_context,
1768
+ add_session_state_to_context=add_session_state_to_context,
1769
+ add_dependencies_to_context=add_dependencies_to_context,
1770
+ stream=True,
1771
+ stream_events=stream_events,
1772
+ )
1757
1773
 
1758
- # Check for cancellation before model processing
1759
- raise_if_cancelled(run_response.run_id) # type: ignore
1774
+ # 3. Prepare run messages
1775
+ run_messages: RunMessages = self._get_run_messages(
1776
+ run_response=run_response,
1777
+ run_context=run_context,
1778
+ session=session,
1779
+ user_id=user_id,
1780
+ input_message=run_input.input_content,
1781
+ audio=run_input.audios,
1782
+ images=run_input.images,
1783
+ videos=run_input.videos,
1784
+ files=run_input.files,
1785
+ add_history_to_context=add_history_to_context,
1786
+ add_dependencies_to_context=add_dependencies_to_context,
1787
+ add_session_state_to_context=add_session_state_to_context,
1788
+ tools=_tools,
1789
+ **kwargs,
1790
+ )
1791
+ if len(run_messages.messages) == 0:
1792
+ log_error("No messages to be sent to the model.")
1760
1793
 
1761
- # 6. Get a response from the model
1762
- if self.output_model is None:
1763
- for event in self._handle_model_response_stream(
1764
- session=session,
1765
- run_response=run_response,
1766
- run_messages=run_messages,
1767
- tools=_tools,
1768
- response_format=response_format,
1769
- stream_events=stream_events,
1770
- session_state=run_context.session_state,
1771
- run_context=run_context,
1772
- ):
1773
- raise_if_cancelled(run_response.run_id) # type: ignore
1774
- yield event
1775
- else:
1776
- for event in self._handle_model_response_stream(
1777
- session=session,
1778
- run_response=run_response,
1779
- run_messages=run_messages,
1780
- tools=_tools,
1781
- response_format=response_format,
1782
- stream_events=stream_events,
1783
- session_state=run_context.session_state,
1784
- run_context=run_context,
1785
- ):
1786
- raise_if_cancelled(run_response.run_id) # type: ignore
1787
- from agno.run.team import IntermediateRunContentEvent, RunContentEvent
1794
+ # 4. Start memory creation in background thread
1795
+ memory_future = self._start_memory_future(
1796
+ run_messages=run_messages,
1797
+ user_id=user_id,
1798
+ existing_future=None,
1799
+ )
1788
1800
 
1789
- if isinstance(event, RunContentEvent):
1801
+ # Start the Run by yielding a RunStarted event
1790
1802
  if stream_events:
1791
- yield IntermediateRunContentEvent(
1792
- content=event.content,
1793
- content_type=event.content_type,
1803
+ yield handle_event( # type: ignore
1804
+ create_team_run_started_event(run_response),
1805
+ run_response,
1806
+ events_to_skip=self.events_to_skip,
1807
+ store_events=self.store_events,
1794
1808
  )
1795
- else:
1796
- yield event
1797
1809
 
1798
- for event in self._generate_response_with_output_model_stream(
1799
- session=session,
1800
- run_response=run_response,
1801
- run_messages=run_messages,
1802
- stream_events=stream_events,
1803
- ):
1804
- raise_if_cancelled(run_response.run_id) # type: ignore
1805
- yield event
1810
+ raise_if_cancelled(run_response.run_id) # type: ignore
1806
1811
 
1807
- # Check for cancellation after model processing
1808
- raise_if_cancelled(run_response.run_id) # type: ignore
1812
+ # 5. Reason about the task if reasoning is enabled
1813
+ yield from self._handle_reasoning_stream(
1814
+ run_response=run_response,
1815
+ run_messages=run_messages,
1816
+ stream_events=stream_events,
1817
+ )
1809
1818
 
1810
- # 7. Parse response with parser model if provided
1811
- yield from self._parse_response_with_parser_model_stream(
1812
- session=session, run_response=run_response, stream_events=stream_events, run_context=run_context
1813
- )
1819
+ # Check for cancellation before model processing
1820
+ raise_if_cancelled(run_response.run_id) # type: ignore
1814
1821
 
1815
- # Yield RunContentCompletedEvent
1816
- if stream_events:
1817
- yield handle_event( # type: ignore
1818
- create_team_run_content_completed_event(from_run_response=run_response),
1819
- run_response,
1820
- events_to_skip=self.events_to_skip,
1821
- store_events=self.store_events,
1822
- )
1823
- # Execute post-hooks after output is generated but before response is returned
1824
- if self.post_hooks is not None:
1825
- yield from self._execute_post_hooks(
1826
- hooks=self.post_hooks, # type: ignore
1827
- run_output=run_response,
1828
- run_context=run_context,
1829
- session=session,
1830
- user_id=user_id,
1831
- debug_mode=debug_mode,
1832
- stream_events=stream_events,
1833
- background_tasks=background_tasks,
1834
- **kwargs,
1835
- )
1836
- raise_if_cancelled(run_response.run_id) # type: ignore
1822
+ # 6. Get a response from the model
1823
+ if self.output_model is None:
1824
+ for event in self._handle_model_response_stream(
1825
+ session=session,
1826
+ run_response=run_response,
1827
+ run_messages=run_messages,
1828
+ tools=_tools,
1829
+ response_format=response_format,
1830
+ stream_events=stream_events,
1831
+ session_state=run_context.session_state,
1832
+ run_context=run_context,
1833
+ ):
1834
+ raise_if_cancelled(run_response.run_id) # type: ignore
1835
+ yield event
1836
+ else:
1837
+ for event in self._handle_model_response_stream(
1838
+ session=session,
1839
+ run_response=run_response,
1840
+ run_messages=run_messages,
1841
+ tools=_tools,
1842
+ response_format=response_format,
1843
+ stream_events=stream_events,
1844
+ session_state=run_context.session_state,
1845
+ run_context=run_context,
1846
+ ):
1847
+ raise_if_cancelled(run_response.run_id) # type: ignore
1848
+ from agno.run.team import IntermediateRunContentEvent, RunContentEvent
1849
+
1850
+ if isinstance(event, RunContentEvent):
1851
+ if stream_events:
1852
+ yield IntermediateRunContentEvent(
1853
+ content=event.content,
1854
+ content_type=event.content_type,
1855
+ )
1856
+ else:
1857
+ yield event
1837
1858
 
1838
- # 8. Wait for background memory creation
1839
- yield from wait_for_thread_tasks_stream(
1840
- run_response=run_response,
1841
- memory_future=memory_future,
1842
- stream_events=stream_events,
1843
- events_to_skip=self.events_to_skip, # type: ignore
1844
- store_events=self.store_events,
1845
- )
1859
+ for event in self._generate_response_with_output_model_stream(
1860
+ session=session,
1861
+ run_response=run_response,
1862
+ run_messages=run_messages,
1863
+ stream_events=stream_events,
1864
+ ):
1865
+ raise_if_cancelled(run_response.run_id) # type: ignore
1866
+ yield event
1846
1867
 
1847
- raise_if_cancelled(run_response.run_id) # type: ignore
1848
- # 9. Create session summary
1849
- if self.session_summary_manager is not None:
1850
- # Upsert the RunOutput to Team Session before creating the session summary
1851
- session.upsert_run(run_response=run_response)
1868
+ # Check for cancellation after model processing
1869
+ raise_if_cancelled(run_response.run_id) # type: ignore
1852
1870
 
1853
- if stream_events:
1854
- yield handle_event( # type: ignore
1855
- create_team_session_summary_started_event(from_run_response=run_response),
1856
- run_response,
1857
- events_to_skip=self.events_to_skip,
1858
- store_events=self.store_events,
1859
- )
1860
- try:
1861
- self.session_summary_manager.create_session_summary(session=session)
1862
- except Exception as e:
1863
- log_warning(f"Error in session summary creation: {str(e)}")
1864
- if stream_events:
1865
- yield handle_event( # type: ignore
1866
- create_team_session_summary_completed_event(
1867
- from_run_response=run_response, session_summary=session.summary
1868
- ),
1869
- run_response,
1870
- events_to_skip=self.events_to_skip,
1871
- store_events=self.store_events,
1872
- )
1871
+ # 7. Parse response with parser model if provided
1872
+ yield from self._parse_response_with_parser_model_stream(
1873
+ session=session, run_response=run_response, stream_events=stream_events, run_context=run_context
1874
+ )
1873
1875
 
1874
- raise_if_cancelled(run_response.run_id) # type: ignore
1875
- # Create the run completed event
1876
- completed_event = handle_event(
1877
- create_team_run_completed_event(
1878
- from_run_response=run_response,
1879
- ),
1880
- run_response,
1881
- events_to_skip=self.events_to_skip,
1882
- store_events=self.store_events,
1883
- )
1876
+ # Yield RunContentCompletedEvent
1877
+ if stream_events:
1878
+ yield handle_event( # type: ignore
1879
+ create_team_run_content_completed_event(from_run_response=run_response),
1880
+ run_response,
1881
+ events_to_skip=self.events_to_skip,
1882
+ store_events=self.store_events,
1883
+ )
1884
+ # Execute post-hooks after output is generated but before response is returned
1885
+ if self.post_hooks is not None:
1886
+ yield from self._execute_post_hooks(
1887
+ hooks=self.post_hooks, # type: ignore
1888
+ run_output=run_response,
1889
+ run_context=run_context,
1890
+ session=session,
1891
+ user_id=user_id,
1892
+ debug_mode=debug_mode,
1893
+ stream_events=stream_events,
1894
+ background_tasks=background_tasks,
1895
+ **kwargs,
1896
+ )
1897
+ raise_if_cancelled(run_response.run_id) # type: ignore
1884
1898
 
1885
- # Set the run status to completed
1886
- run_response.status = RunStatus.completed
1899
+ # 8. Wait for background memory creation
1900
+ yield from wait_for_thread_tasks_stream(
1901
+ run_response=run_response,
1902
+ memory_future=memory_future, # type: ignore
1903
+ stream_events=stream_events,
1904
+ events_to_skip=self.events_to_skip, # type: ignore
1905
+ store_events=self.store_events,
1906
+ )
1887
1907
 
1888
- # 10. Cleanup and store the run response
1889
- self._cleanup_and_store(run_response=run_response, session=session)
1908
+ raise_if_cancelled(run_response.run_id) # type: ignore
1909
+ # 9. Create session summary
1910
+ if self.session_summary_manager is not None:
1911
+ # Upsert the RunOutput to Team Session before creating the session summary
1912
+ session.upsert_run(run_response=run_response)
1890
1913
 
1891
- if stream_events:
1892
- yield completed_event
1914
+ if stream_events:
1915
+ yield handle_event( # type: ignore
1916
+ create_team_session_summary_started_event(from_run_response=run_response),
1917
+ run_response,
1918
+ events_to_skip=self.events_to_skip,
1919
+ store_events=self.store_events,
1920
+ )
1921
+ try:
1922
+ self.session_summary_manager.create_session_summary(session=session)
1923
+ except Exception as e:
1924
+ log_warning(f"Error in session summary creation: {str(e)}")
1925
+ if stream_events:
1926
+ yield handle_event( # type: ignore
1927
+ create_team_session_summary_completed_event(
1928
+ from_run_response=run_response, session_summary=session.summary
1929
+ ),
1930
+ run_response,
1931
+ events_to_skip=self.events_to_skip,
1932
+ store_events=self.store_events,
1933
+ )
1934
+
1935
+ raise_if_cancelled(run_response.run_id) # type: ignore
1936
+ # Create the run completed event
1937
+ completed_event = handle_event(
1938
+ create_team_run_completed_event(
1939
+ from_run_response=run_response,
1940
+ ),
1941
+ run_response,
1942
+ events_to_skip=self.events_to_skip,
1943
+ store_events=self.store_events,
1944
+ )
1945
+
1946
+ # Set the run status to completed
1947
+ run_response.status = RunStatus.completed
1948
+
1949
+ # 10. Cleanup and store the run response
1950
+ self._cleanup_and_store(run_response=run_response, session=session)
1951
+
1952
+ if stream_events:
1953
+ yield completed_event
1954
+
1955
+ if yield_run_output:
1956
+ yield run_response
1957
+
1958
+ # Log Team Telemetry
1959
+ self._log_team_telemetry(session_id=session.session_id, run_id=run_response.run_id)
1960
+
1961
+ log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
1962
+
1963
+ break
1964
+ except RunCancelledException as e:
1965
+ # Handle run cancellation during streaming
1966
+ log_info(f"Team run {run_response.run_id} was cancelled during streaming")
1967
+ run_response.status = RunStatus.cancelled
1968
+ run_response.content = str(e)
1969
+
1970
+ # Yield the cancellation event
1971
+ yield handle_event(
1972
+ create_team_run_cancelled_event(from_run_response=run_response, reason=str(e)),
1973
+ run_response,
1974
+ events_to_skip=self.events_to_skip,
1975
+ store_events=self.store_events,
1976
+ )
1977
+ self._cleanup_and_store(run_response=run_response, session=session)
1978
+ break
1979
+ except (InputCheckError, OutputCheckError) as e:
1980
+ run_response.status = RunStatus.error
1981
+
1982
+ # Add error event to list of events
1983
+ run_error = create_team_run_error_event(
1984
+ run_response,
1985
+ error=str(e),
1986
+ error_id=e.error_id,
1987
+ error_type=e.type,
1988
+ additional_data=e.additional_data,
1989
+ )
1990
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
1991
+
1992
+ if run_response.content is None:
1993
+ run_response.content = str(e)
1994
+ self._cleanup_and_store(run_response=run_response, session=session)
1995
+ yield run_error
1996
+ break
1997
+
1998
+ except KeyboardInterrupt:
1999
+ run_response = cast(TeamRunOutput, run_response)
2000
+ yield handle_event( # type: ignore
2001
+ create_team_run_cancelled_event(
2002
+ from_run_response=run_response, reason="Operation cancelled by user"
2003
+ ),
2004
+ run_response,
2005
+ events_to_skip=self.events_to_skip, # type: ignore
2006
+ store_events=self.store_events,
2007
+ )
2008
+ break
2009
+ except Exception as e:
2010
+ if attempt < num_attempts - 1:
2011
+ # Calculate delay with exponential backoff if enabled
2012
+ if self.exponential_backoff:
2013
+ delay = self.delay_between_retries * (2**attempt)
2014
+ else:
2015
+ delay = self.delay_between_retries
1893
2016
 
1894
- if yield_run_output:
1895
- yield run_response
2017
+ log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2018
+ time.sleep(delay)
2019
+ continue
1896
2020
 
1897
- # Log Team Telemetry
1898
- self._log_team_telemetry(session_id=session.session_id, run_id=run_response.run_id)
2021
+ run_response.status = RunStatus.error
2022
+ run_error = create_team_run_error_event(run_response, error=str(e))
2023
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2024
+ if run_response.content is None:
2025
+ run_response.content = str(e)
1899
2026
 
1900
- log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
2027
+ log_error(f"Error in Team run: {str(e)}")
1901
2028
 
1902
- # Disconnect tools and clean up run tracking
1903
- self._disconnect_connectable_tools()
1904
- cleanup_run(run_response.run_id) # type: ignore
2029
+ self._cleanup_and_store(run_response=run_response, session=session)
2030
+ yield run_error
2031
+ finally:
2032
+ # Cancel background futures on error (wait_for_thread_tasks_stream handles waiting on success)
2033
+ if memory_future is not None and not memory_future.done():
2034
+ memory_future.cancel()
2035
+
2036
+ # Always disconnect connectable tools
2037
+ self._disconnect_connectable_tools()
2038
+ # Always clean up the run tracking
2039
+ cleanup_run(run_response.run_id) # type: ignore
1905
2040
 
1906
2041
  @overload
1907
2042
  def run(
@@ -2011,288 +2146,173 @@ class Team:
2011
2146
  )
2012
2147
  yield_run_output = yield_run_output or yield_run_response # For backwards compatibility
2013
2148
 
2014
- # Set up retry logic
2015
- num_attempts = self.retries + 1
2016
- for attempt in range(num_attempts):
2017
- if num_attempts > 1:
2018
- log_debug(f"Retrying Team run {run_id}. Attempt {attempt + 1} of {num_attempts}...")
2019
-
2020
- try:
2021
- # Register run for cancellation tracking
2022
- register_run(run_id) # type: ignore
2023
-
2024
- background_tasks = kwargs.pop("background_tasks", None)
2025
- if background_tasks is not None:
2026
- from fastapi import BackgroundTasks
2027
-
2028
- background_tasks: BackgroundTasks = background_tasks # type: ignore
2029
-
2030
- # Validate input against input_schema if provided
2031
- validated_input = validate_input(input, self.input_schema)
2032
-
2033
- # Normalise hook & guardails
2034
- if not self._hooks_normalised:
2035
- if self.pre_hooks:
2036
- self.pre_hooks = normalize_pre_hooks(self.pre_hooks) # type: ignore
2037
- if self.post_hooks:
2038
- self.post_hooks = normalize_post_hooks(self.post_hooks) # type: ignore
2039
- self._hooks_normalised = True
2040
-
2041
- session_id, user_id = self._initialize_session(session_id=session_id, user_id=user_id)
2042
-
2043
- image_artifacts, video_artifacts, audio_artifacts, file_artifacts = validate_media_object_id(
2044
- images=images, videos=videos, audios=audio, files=files
2045
- )
2046
-
2047
- # Create RunInput to capture the original user input
2048
- run_input = TeamRunInput(
2049
- input_content=validated_input,
2050
- images=image_artifacts,
2051
- videos=video_artifacts,
2052
- audios=audio_artifacts,
2053
- files=file_artifacts,
2054
- )
2055
-
2056
- # Read existing session from database
2057
- team_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2058
- self._update_metadata(session=team_session)
2059
-
2060
- # Initialize session state
2061
- session_state = self._initialize_session_state(
2062
- session_state=session_state if session_state is not None else {},
2063
- user_id=user_id,
2064
- session_id=session_id,
2065
- run_id=run_id,
2066
- )
2067
- # Update session state from DB
2068
- session_state = self._load_session_state(session=team_session, session_state=session_state)
2069
-
2070
- # Determine runtime dependencies
2071
- dependencies = dependencies if dependencies is not None else self.dependencies
2149
+ # Register run for cancellation tracking
2150
+ register_run(run_id) # type: ignore
2072
2151
 
2073
- # Resolve output_schema parameter takes precedence, then fall back to self.output_schema
2074
- if output_schema is None:
2075
- output_schema = self.output_schema
2152
+ background_tasks = kwargs.pop("background_tasks", None)
2153
+ if background_tasks is not None:
2154
+ from fastapi import BackgroundTasks
2076
2155
 
2077
- # Initialize run context
2078
- run_context = run_context or RunContext(
2079
- run_id=run_id,
2080
- session_id=session_id,
2081
- user_id=user_id,
2082
- session_state=session_state,
2083
- dependencies=dependencies,
2084
- output_schema=output_schema,
2085
- )
2086
- # output_schema parameter takes priority, even if run_context was provided
2087
- run_context.output_schema = output_schema
2088
-
2089
- # Resolve callable dependencies if present
2090
- if run_context.dependencies is not None:
2091
- self._resolve_run_dependencies(run_context=run_context)
2092
-
2093
- # Determine runtime context parameters
2094
- add_dependencies = (
2095
- add_dependencies_to_context
2096
- if add_dependencies_to_context is not None
2097
- else self.add_dependencies_to_context
2098
- )
2099
- add_session_state = (
2100
- add_session_state_to_context
2101
- if add_session_state_to_context is not None
2102
- else self.add_session_state_to_context
2103
- )
2104
- add_history = (
2105
- add_history_to_context if add_history_to_context is not None else self.add_history_to_context
2106
- )
2156
+ background_tasks: BackgroundTasks = background_tasks # type: ignore
2107
2157
 
2108
- # When filters are passed manually
2109
- if self.knowledge_filters or knowledge_filters:
2110
- run_context.knowledge_filters = self._get_effective_filters(knowledge_filters)
2158
+ # Validate input against input_schema if provided
2159
+ validated_input = validate_input(input, self.input_schema)
2111
2160
 
2112
- # Use stream override value when necessary
2113
- if stream is None:
2114
- stream = False if self.stream is None else self.stream
2161
+ # Normalise hook & guardails
2162
+ if not self._hooks_normalised:
2163
+ if self.pre_hooks:
2164
+ self.pre_hooks = normalize_pre_hooks(self.pre_hooks) # type: ignore
2165
+ if self.post_hooks:
2166
+ self.post_hooks = normalize_post_hooks(self.post_hooks) # type: ignore
2167
+ self._hooks_normalised = True
2115
2168
 
2116
- # Considering both stream_events and stream_intermediate_steps (deprecated)
2117
- stream_events = stream_events or stream_intermediate_steps
2169
+ session_id, user_id = self._initialize_session(session_id=session_id, user_id=user_id)
2118
2170
 
2119
- # Can't stream events if streaming is disabled
2120
- if stream is False:
2121
- stream_events = False
2171
+ image_artifacts, video_artifacts, audio_artifacts, file_artifacts = validate_media_object_id(
2172
+ images=images, videos=videos, audios=audio, files=files
2173
+ )
2122
2174
 
2123
- if stream_events is None:
2124
- stream_events = False if self.stream_events is None else self.stream_events
2175
+ # Create RunInput to capture the original user input
2176
+ run_input = TeamRunInput(
2177
+ input_content=validated_input,
2178
+ images=image_artifacts,
2179
+ videos=video_artifacts,
2180
+ audios=audio_artifacts,
2181
+ files=file_artifacts,
2182
+ )
2125
2183
 
2126
- self.model = cast(Model, self.model)
2184
+ # Read existing session from database
2185
+ team_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2186
+ self._update_metadata(session=team_session)
2127
2187
 
2128
- if self.metadata is not None:
2129
- if metadata is None:
2130
- metadata = self.metadata
2131
- else:
2132
- merge_dictionaries(metadata, self.metadata)
2188
+ # Initialize session state
2189
+ session_state = self._initialize_session_state(
2190
+ session_state=session_state if session_state is not None else {},
2191
+ user_id=user_id,
2192
+ session_id=session_id,
2193
+ run_id=run_id,
2194
+ )
2195
+ # Update session state from DB
2196
+ session_state = self._load_session_state(session=team_session, session_state=session_state)
2133
2197
 
2134
- if metadata:
2135
- run_context.metadata = metadata
2198
+ # Determine runtime dependencies
2199
+ dependencies = dependencies if dependencies is not None else self.dependencies
2136
2200
 
2137
- # Configure the model for runs
2138
- response_format: Optional[Union[Dict, Type[BaseModel]]] = (
2139
- self._get_response_format(run_context=run_context) if self.parser_model is None else None
2140
- )
2201
+ # Resolve output_schema parameter takes precedence, then fall back to self.output_schema
2202
+ if output_schema is None:
2203
+ output_schema = self.output_schema
2141
2204
 
2142
- # Create a new run_response for this attempt
2143
- run_response = TeamRunOutput(
2144
- run_id=run_id,
2145
- session_id=session_id,
2146
- user_id=user_id,
2147
- team_id=self.id,
2148
- team_name=self.name,
2149
- metadata=run_context.metadata,
2150
- session_state=run_context.session_state,
2151
- input=run_input,
2152
- )
2205
+ # Initialize run context
2206
+ run_context = run_context or RunContext(
2207
+ run_id=run_id,
2208
+ session_id=session_id,
2209
+ user_id=user_id,
2210
+ session_state=session_state,
2211
+ dependencies=dependencies,
2212
+ output_schema=output_schema,
2213
+ )
2214
+ # output_schema parameter takes priority, even if run_context was provided
2215
+ run_context.output_schema = output_schema
2153
2216
 
2154
- run_response.model = self.model.id if self.model is not None else None
2155
- run_response.model_provider = self.model.provider if self.model is not None else None
2217
+ # Resolve callable dependencies if present
2218
+ if run_context.dependencies is not None:
2219
+ self._resolve_run_dependencies(run_context=run_context)
2156
2220
 
2157
- # Start the run metrics timer, to calculate the run duration
2158
- run_response.metrics = Metrics()
2159
- run_response.metrics.start_timer()
2221
+ # Determine runtime context parameters
2222
+ add_dependencies = (
2223
+ add_dependencies_to_context if add_dependencies_to_context is not None else self.add_dependencies_to_context
2224
+ )
2225
+ add_session_state = (
2226
+ add_session_state_to_context
2227
+ if add_session_state_to_context is not None
2228
+ else self.add_session_state_to_context
2229
+ )
2230
+ add_history = add_history_to_context if add_history_to_context is not None else self.add_history_to_context
2160
2231
 
2161
- if stream:
2162
- return self._run_stream(
2163
- run_response=run_response,
2164
- run_context=run_context,
2165
- session=team_session,
2166
- user_id=user_id,
2167
- add_history_to_context=add_history,
2168
- add_dependencies_to_context=add_dependencies,
2169
- add_session_state_to_context=add_session_state,
2170
- response_format=response_format,
2171
- stream_events=stream_events,
2172
- yield_run_output=yield_run_output,
2173
- debug_mode=debug_mode,
2174
- background_tasks=background_tasks,
2175
- **kwargs,
2176
- ) # type: ignore
2232
+ # When filters are passed manually
2233
+ if self.knowledge_filters or knowledge_filters:
2234
+ run_context.knowledge_filters = self._get_effective_filters(knowledge_filters)
2177
2235
 
2178
- else:
2179
- return self._run(
2180
- run_response=run_response,
2181
- run_context=run_context,
2182
- session=team_session,
2183
- user_id=user_id,
2184
- add_history_to_context=add_history,
2185
- add_dependencies_to_context=add_dependencies,
2186
- add_session_state_to_context=add_session_state,
2187
- response_format=response_format,
2188
- debug_mode=debug_mode,
2189
- background_tasks=background_tasks,
2190
- **kwargs,
2191
- )
2192
- except InputCheckError as e:
2193
- run_response.status = RunStatus.error
2194
- if stream:
2195
- run_error = create_team_run_error_event(
2196
- run_response,
2197
- error=str(e),
2198
- error_id=e.error_id,
2199
- error_type=e.type,
2200
- additional_data=e.additional_data,
2201
- )
2202
- run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2203
- if run_response.content is None:
2204
- run_response.content = str(e)
2236
+ # Use stream override value when necessary
2237
+ if stream is None:
2238
+ stream = False if self.stream is None else self.stream
2205
2239
 
2206
- log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
2240
+ # Considering both stream_events and stream_intermediate_steps (deprecated)
2241
+ stream_events = stream_events or stream_intermediate_steps
2207
2242
 
2208
- if stream:
2209
- return generator_wrapper(run_error) # type: ignore
2210
- else:
2211
- return run_response
2212
- except RunCancelledException as e:
2213
- # Handle run cancellation during streaming
2214
- log_info(f"Team run {run_response.run_id} was cancelled during streaming")
2215
- run_response.status = RunStatus.cancelled
2216
- run_response.content = str(e)
2243
+ # Can't stream events if streaming is disabled
2244
+ if stream is False:
2245
+ stream_events = False
2217
2246
 
2218
- # Yield the cancellation event
2219
- if stream:
2220
- cancelled_run_error = handle_event(
2221
- create_team_run_cancelled_event(from_run_response=run_response, reason=str(e)),
2222
- run_response,
2223
- events_to_skip=self.events_to_skip,
2224
- store_events=self.store_events,
2225
- )
2226
- return generator_wrapper(cancelled_run_error) # type: ignore
2227
- else:
2228
- return run_response
2229
- except KeyboardInterrupt:
2230
- # Handle KeyboardInterrupt - stop retries immediately
2231
- run_response.status = RunStatus.cancelled
2232
- run_response.content = "Operation cancelled by user"
2233
- if stream:
2234
- cancelled_run_error = handle_event(
2235
- create_team_run_cancelled_event(
2236
- from_run_response=run_response, reason="Operation cancelled by user"
2237
- ),
2238
- run_response,
2239
- events_to_skip=self.events_to_skip,
2240
- store_events=self.store_events,
2241
- )
2242
- return generator_wrapper(cancelled_run_error) # type: ignore
2243
- else:
2244
- return run_response
2245
- except (InputCheckError, OutputCheckError) as e:
2246
- run_response.status = RunStatus.error
2247
+ if stream_events is None:
2248
+ stream_events = False if self.stream_events is None else self.stream_events
2247
2249
 
2248
- if stream:
2249
- # Add error event to list of events
2250
- run_error = create_team_run_error_event(
2251
- run_response,
2252
- error=str(e),
2253
- error_id=e.error_id,
2254
- error_type=e.type,
2255
- additional_data=e.additional_data,
2256
- )
2257
- run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2250
+ self.model = cast(Model, self.model)
2258
2251
 
2259
- if run_response.content is None:
2260
- run_response.content = str(e)
2252
+ if self.metadata is not None:
2253
+ if metadata is None:
2254
+ metadata = self.metadata
2255
+ else:
2256
+ merge_dictionaries(metadata, self.metadata)
2261
2257
 
2262
- log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
2258
+ if metadata:
2259
+ run_context.metadata = metadata
2263
2260
 
2264
- if stream:
2265
- return generator_wrapper(run_error) # type: ignore
2266
- else:
2267
- return run_response
2268
- except Exception as e:
2269
- if attempt < num_attempts - 1:
2270
- # Calculate delay with exponential backoff if enabled
2271
- if self.exponential_backoff:
2272
- delay = self.delay_between_retries * (2**attempt)
2273
- else:
2274
- delay = self.delay_between_retries
2261
+ # Configure the model for runs
2262
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = (
2263
+ self._get_response_format(run_context=run_context) if self.parser_model is None else None
2264
+ )
2275
2265
 
2276
- log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2277
- time.sleep(delay)
2278
- continue
2266
+ # Create a new run_response for this attempt
2267
+ run_response = TeamRunOutput(
2268
+ run_id=run_id,
2269
+ session_id=session_id,
2270
+ user_id=user_id,
2271
+ team_id=self.id,
2272
+ team_name=self.name,
2273
+ metadata=run_context.metadata,
2274
+ session_state=run_context.session_state,
2275
+ input=run_input,
2276
+ )
2279
2277
 
2280
- run_response.status = RunStatus.error
2281
- if stream:
2282
- run_error = create_team_run_error_event(run_response, error=str(e))
2283
- run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2284
- if run_response.content is None:
2285
- run_response.content = str(e)
2278
+ run_response.model = self.model.id if self.model is not None else None
2279
+ run_response.model_provider = self.model.provider if self.model is not None else None
2286
2280
 
2287
- log_error(f"Error in Team run: {str(e)}")
2281
+ # Start the run metrics timer, to calculate the run duration
2282
+ run_response.metrics = Metrics()
2283
+ run_response.metrics.start_timer()
2288
2284
 
2289
- if stream:
2290
- return generator_wrapper(run_error) # type: ignore
2291
- else:
2292
- return run_response
2285
+ if stream:
2286
+ return self._run_stream(
2287
+ run_response=run_response,
2288
+ run_context=run_context,
2289
+ session=team_session,
2290
+ user_id=user_id,
2291
+ add_history_to_context=add_history,
2292
+ add_dependencies_to_context=add_dependencies,
2293
+ add_session_state_to_context=add_session_state,
2294
+ response_format=response_format,
2295
+ stream_events=stream_events,
2296
+ yield_run_output=yield_run_output,
2297
+ debug_mode=debug_mode,
2298
+ background_tasks=background_tasks,
2299
+ **kwargs,
2300
+ ) # type: ignore
2293
2301
 
2294
- # If we get here, all retries failed (shouldn't happen with current logic)
2295
- raise Exception(f"Failed after {num_attempts} attempts.")
2302
+ else:
2303
+ return self._run(
2304
+ run_response=run_response,
2305
+ run_context=run_context,
2306
+ session=team_session,
2307
+ user_id=user_id,
2308
+ add_history_to_context=add_history,
2309
+ add_dependencies_to_context=add_dependencies,
2310
+ add_session_state_to_context=add_session_state,
2311
+ response_format=response_format,
2312
+ debug_mode=debug_mode,
2313
+ background_tasks=background_tasks,
2314
+ **kwargs,
2315
+ )
2296
2316
 
2297
2317
  async def _arun(
2298
2318
  self,
@@ -2327,275 +2347,272 @@ class Team:
2327
2347
  14. Create session summary
2328
2348
  15. Cleanup and store (scrub, add to session, calculate metrics, save session)
2329
2349
  """
2350
+ await aregister_run(run_context.run_id)
2330
2351
  log_debug(f"Team Run Start: {run_response.run_id}", center=True)
2331
2352
  memory_task = None
2332
2353
 
2333
- # Set up retry logic
2334
- num_attempts = self.retries + 1
2335
- for attempt in range(num_attempts):
2336
- if num_attempts > 1:
2337
- log_debug(f"Retrying Team run {run_response.run_id}. Attempt {attempt + 1} of {num_attempts}...")
2354
+ try:
2355
+ # Set up retry logic
2356
+ num_attempts = self.retries + 1
2357
+ for attempt in range(num_attempts):
2358
+ if num_attempts > 1:
2359
+ log_debug(f"Retrying Team run {run_response.run_id}. Attempt {attempt + 1} of {num_attempts}...")
2338
2360
 
2339
- try:
2340
- if run_context.dependencies is not None:
2341
- await self._aresolve_run_dependencies(run_context=run_context)
2361
+ try:
2362
+ if run_context.dependencies is not None:
2363
+ await self._aresolve_run_dependencies(run_context=run_context)
2342
2364
 
2343
- # 1. Read or create session. Reads from the database if provided.
2344
- if self._has_async_db():
2345
- team_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
2346
- else:
2347
- team_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2365
+ # 1. Read or create session. Reads from the database if provided.
2366
+ if self._has_async_db():
2367
+ team_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
2368
+ else:
2369
+ team_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2348
2370
 
2349
- # 2. Update metadata and session state
2350
- self._update_metadata(session=team_session)
2351
- # Initialize session state
2352
- run_context.session_state = self._initialize_session_state(
2353
- session_state=run_context.session_state if run_context.session_state is not None else {},
2354
- user_id=user_id,
2355
- session_id=session_id,
2356
- run_id=run_response.run_id,
2357
- )
2358
- # Update session state from DB
2359
- if run_context.session_state is not None:
2360
- run_context.session_state = self._load_session_state(
2361
- session=team_session, session_state=run_context.session_state
2371
+ # 2. Update metadata and session state
2372
+ self._update_metadata(session=team_session)
2373
+ # Initialize session state
2374
+ run_context.session_state = self._initialize_session_state(
2375
+ session_state=run_context.session_state if run_context.session_state is not None else {},
2376
+ user_id=user_id,
2377
+ session_id=session_id,
2378
+ run_id=run_response.run_id,
2362
2379
  )
2380
+ # Update session state from DB
2381
+ if run_context.session_state is not None:
2382
+ run_context.session_state = self._load_session_state(
2383
+ session=team_session, session_state=run_context.session_state
2384
+ )
2385
+
2386
+ run_input = cast(TeamRunInput, run_response.input)
2387
+
2388
+ # 3. Execute pre-hooks after session is loaded but before processing starts
2389
+ if self.pre_hooks is not None:
2390
+ pre_hook_iterator = self._aexecute_pre_hooks(
2391
+ hooks=self.pre_hooks, # type: ignore
2392
+ run_response=run_response,
2393
+ run_context=run_context,
2394
+ run_input=run_input,
2395
+ session=team_session,
2396
+ user_id=user_id,
2397
+ debug_mode=debug_mode,
2398
+ background_tasks=background_tasks,
2399
+ **kwargs,
2400
+ )
2363
2401
 
2364
- run_input = cast(TeamRunInput, run_response.input)
2402
+ # Consume the async iterator without yielding
2403
+ async for _ in pre_hook_iterator:
2404
+ pass
2365
2405
 
2366
- # 3. Execute pre-hooks after session is loaded but before processing starts
2367
- if self.pre_hooks is not None:
2368
- pre_hook_iterator = self._aexecute_pre_hooks(
2369
- hooks=self.pre_hooks, # type: ignore
2406
+ # 4. Determine tools for model
2407
+ team_run_context: Dict[str, Any] = {}
2408
+ self.model = cast(Model, self.model)
2409
+ await self._check_and_refresh_mcp_tools()
2410
+ _tools = self._determine_tools_for_model(
2411
+ model=self.model,
2370
2412
  run_response=run_response,
2371
2413
  run_context=run_context,
2372
- run_input=run_input,
2414
+ team_run_context=team_run_context,
2373
2415
  session=team_session,
2374
2416
  user_id=user_id,
2417
+ async_mode=True,
2418
+ input_message=run_input.input_content,
2419
+ images=run_input.images,
2420
+ videos=run_input.videos,
2421
+ audio=run_input.audios,
2422
+ files=run_input.files,
2375
2423
  debug_mode=debug_mode,
2376
- background_tasks=background_tasks,
2377
- **kwargs,
2424
+ add_history_to_context=add_history_to_context,
2425
+ add_dependencies_to_context=add_dependencies_to_context,
2426
+ add_session_state_to_context=add_session_state_to_context,
2427
+ stream=False,
2428
+ stream_events=False,
2378
2429
  )
2379
2430
 
2380
- # Consume the async iterator without yielding
2381
- async for _ in pre_hook_iterator:
2382
- pass
2383
-
2384
- # 4. Determine tools for model
2385
- team_run_context: Dict[str, Any] = {}
2386
- self.model = cast(Model, self.model)
2387
- await self._check_and_refresh_mcp_tools()
2388
- _tools = self._determine_tools_for_model(
2389
- model=self.model,
2390
- run_response=run_response,
2391
- run_context=run_context,
2392
- team_run_context=team_run_context,
2393
- session=team_session,
2394
- user_id=user_id,
2395
- async_mode=True,
2396
- input_message=run_input.input_content,
2397
- images=run_input.images,
2398
- videos=run_input.videos,
2399
- audio=run_input.audios,
2400
- files=run_input.files,
2401
- debug_mode=debug_mode,
2402
- add_history_to_context=add_history_to_context,
2403
- add_dependencies_to_context=add_dependencies_to_context,
2404
- add_session_state_to_context=add_session_state_to_context,
2405
- stream=False,
2406
- stream_events=False,
2407
- )
2408
-
2409
- # 5. Prepare run messages
2410
- run_messages = await self._aget_run_messages(
2411
- run_response=run_response,
2412
- run_context=run_context,
2413
- session=team_session, # type: ignore
2414
- user_id=user_id,
2415
- input_message=run_input.input_content,
2416
- audio=run_input.audios,
2417
- images=run_input.images,
2418
- videos=run_input.videos,
2419
- files=run_input.files,
2420
- add_history_to_context=add_history_to_context,
2421
- add_dependencies_to_context=add_dependencies_to_context,
2422
- add_session_state_to_context=add_session_state_to_context,
2423
- tools=_tools,
2424
- **kwargs,
2425
- )
2431
+ # 5. Prepare run messages
2432
+ run_messages = await self._aget_run_messages(
2433
+ run_response=run_response,
2434
+ run_context=run_context,
2435
+ session=team_session, # type: ignore
2436
+ user_id=user_id,
2437
+ input_message=run_input.input_content,
2438
+ audio=run_input.audios,
2439
+ images=run_input.images,
2440
+ videos=run_input.videos,
2441
+ files=run_input.files,
2442
+ add_history_to_context=add_history_to_context,
2443
+ add_dependencies_to_context=add_dependencies_to_context,
2444
+ add_session_state_to_context=add_session_state_to_context,
2445
+ tools=_tools,
2446
+ **kwargs,
2447
+ )
2426
2448
 
2427
- self.model = cast(Model, self.model)
2428
- log_debug(f"Team Run Start: {run_response.run_id}", center=True)
2449
+ self.model = cast(Model, self.model)
2429
2450
 
2430
- # 6. Start memory creation in background task
2431
- memory_task = None
2432
- if (
2433
- run_messages.user_message is not None
2434
- and self.memory_manager is not None
2435
- and self.enable_user_memories
2436
- and not self.enable_agentic_memory
2437
- ):
2438
- log_debug("Starting memory creation in background task.")
2439
- memory_task = asyncio.create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2440
-
2441
- raise_if_cancelled(run_response.run_id) # type: ignore
2442
- # 7. Reason about the task if reasoning is enabled
2443
- await self._ahandle_reasoning(run_response=run_response, run_messages=run_messages)
2444
-
2445
- # Check for cancellation before model call
2446
- raise_if_cancelled(run_response.run_id) # type: ignore
2447
-
2448
- # 8. Get the model response for the team leader
2449
- model_response = await self.model.aresponse(
2450
- messages=run_messages.messages,
2451
- tools=_tools,
2452
- tool_choice=self.tool_choice,
2453
- tool_call_limit=self.tool_call_limit,
2454
- response_format=response_format,
2455
- send_media_to_model=self.send_media_to_model,
2456
- run_response=run_response,
2457
- compression_manager=self.compression_manager if self.compress_tool_results else None,
2458
- ) # type: ignore
2451
+ # 6. Start memory creation in background task
2452
+ memory_task = await self._astart_memory_task(
2453
+ run_messages=run_messages,
2454
+ user_id=user_id,
2455
+ existing_task=memory_task,
2456
+ )
2459
2457
 
2460
- # Check for cancellation after model call
2461
- raise_if_cancelled(run_response.run_id) # type: ignore
2458
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2459
+ # 7. Reason about the task if reasoning is enabled
2460
+ await self._ahandle_reasoning(run_response=run_response, run_messages=run_messages)
2462
2461
 
2463
- # If an output model is provided, generate output using the output model
2464
- await self._agenerate_response_with_output_model(
2465
- model_response=model_response, run_messages=run_messages
2466
- )
2462
+ # Check for cancellation before model call
2463
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2467
2464
 
2468
- # If a parser model is provided, structure the response separately
2469
- await self._aparse_response_with_parser_model(
2470
- model_response=model_response, run_messages=run_messages, run_context=run_context
2471
- )
2465
+ # 8. Get the model response for the team leader
2466
+ model_response = await self.model.aresponse(
2467
+ messages=run_messages.messages,
2468
+ tools=_tools,
2469
+ tool_choice=self.tool_choice,
2470
+ tool_call_limit=self.tool_call_limit,
2471
+ response_format=response_format,
2472
+ send_media_to_model=self.send_media_to_model,
2473
+ run_response=run_response,
2474
+ compression_manager=self.compression_manager if self.compress_tool_results else None,
2475
+ ) # type: ignore
2472
2476
 
2473
- # 9. Update TeamRunOutput with the model response
2474
- self._update_run_response(
2475
- model_response=model_response,
2476
- run_response=run_response,
2477
- run_messages=run_messages,
2478
- run_context=run_context,
2479
- )
2477
+ # Check for cancellation after model call
2478
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2480
2479
 
2481
- # 10. Store media if enabled
2482
- if self.store_media:
2483
- store_media_util(run_response, model_response)
2480
+ # If an output model is provided, generate output using the output model
2481
+ await self._agenerate_response_with_output_model(
2482
+ model_response=model_response, run_messages=run_messages
2483
+ )
2484
2484
 
2485
- # 11. Convert response to structured format
2486
- self._convert_response_to_structured_format(run_response=run_response, run_context=run_context)
2485
+ # If a parser model is provided, structure the response separately
2486
+ await self._aparse_response_with_parser_model(
2487
+ model_response=model_response, run_messages=run_messages, run_context=run_context
2488
+ )
2487
2489
 
2488
- # 12. Execute post-hooks after output is generated but before response is returned
2489
- if self.post_hooks is not None:
2490
- async for _ in self._aexecute_post_hooks(
2491
- hooks=self.post_hooks, # type: ignore
2492
- run_output=run_response,
2490
+ # 9. Update TeamRunOutput with the model response
2491
+ self._update_run_response(
2492
+ model_response=model_response,
2493
+ run_response=run_response,
2494
+ run_messages=run_messages,
2493
2495
  run_context=run_context,
2494
- session=team_session,
2495
- user_id=user_id,
2496
- debug_mode=debug_mode,
2497
- background_tasks=background_tasks,
2498
- **kwargs,
2499
- ):
2500
- pass
2496
+ )
2501
2497
 
2502
- raise_if_cancelled(run_response.run_id) # type: ignore
2498
+ # 10. Store media if enabled
2499
+ if self.store_media:
2500
+ store_media_util(run_response, model_response)
2503
2501
 
2504
- # 13. Wait for background memory creation
2505
- await await_for_open_threads(memory_task=memory_task)
2502
+ # 11. Convert response to structured format
2503
+ self._convert_response_to_structured_format(run_response=run_response, run_context=run_context)
2506
2504
 
2507
- raise_if_cancelled(run_response.run_id) # type: ignore
2508
- # 14. Create session summary
2509
- if self.session_summary_manager is not None:
2510
- # Upsert the RunOutput to Team Session before creating the session summary
2511
- team_session.upsert_run(run_response=run_response)
2512
- try:
2513
- await self.session_summary_manager.acreate_session_summary(session=team_session)
2514
- except Exception as e:
2515
- log_warning(f"Error in session summary creation: {str(e)}")
2505
+ # 12. Execute post-hooks after output is generated but before response is returned
2506
+ if self.post_hooks is not None:
2507
+ async for _ in self._aexecute_post_hooks(
2508
+ hooks=self.post_hooks, # type: ignore
2509
+ run_output=run_response,
2510
+ run_context=run_context,
2511
+ session=team_session,
2512
+ user_id=user_id,
2513
+ debug_mode=debug_mode,
2514
+ background_tasks=background_tasks,
2515
+ **kwargs,
2516
+ ):
2517
+ pass
2516
2518
 
2517
- raise_if_cancelled(run_response.run_id) # type: ignore
2518
- run_response.status = RunStatus.completed
2519
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2519
2520
 
2520
- # 15. Cleanup and store the run response and session
2521
- await self._acleanup_and_store(run_response=run_response, session=team_session)
2521
+ # 13. Wait for background memory creation
2522
+ await await_for_open_threads(memory_task=memory_task)
2522
2523
 
2523
- # Log Team Telemetry
2524
- await self._alog_team_telemetry(session_id=team_session.session_id, run_id=run_response.run_id)
2524
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2525
+ # 14. Create session summary
2526
+ if self.session_summary_manager is not None:
2527
+ # Upsert the RunOutput to Team Session before creating the session summary
2528
+ team_session.upsert_run(run_response=run_response)
2529
+ try:
2530
+ await self.session_summary_manager.acreate_session_summary(session=team_session)
2531
+ except Exception as e:
2532
+ log_warning(f"Error in session summary creation: {str(e)}")
2525
2533
 
2526
- log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
2534
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2535
+ run_response.status = RunStatus.completed
2527
2536
 
2528
- return run_response
2537
+ # 15. Cleanup and store the run response and session
2538
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2529
2539
 
2530
- except RunCancelledException as e:
2531
- # Handle run cancellation
2532
- log_info(f"Run {run_response.run_id} was cancelled")
2533
- run_response.content = str(e)
2534
- run_response.status = RunStatus.cancelled
2540
+ # Log Team Telemetry
2541
+ await self._alog_team_telemetry(session_id=team_session.session_id, run_id=run_response.run_id)
2535
2542
 
2536
- # Cleanup and store the run response and session
2537
- await self._acleanup_and_store(run_response=run_response, session=team_session)
2543
+ log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
2538
2544
 
2539
- return run_response
2545
+ return run_response
2540
2546
 
2541
- except (InputCheckError, OutputCheckError) as e:
2542
- run_response.status = RunStatus.error
2543
- run_error = create_team_run_error_event(
2544
- run_response,
2545
- error=str(e),
2546
- error_id=e.error_id,
2547
- error_type=e.type,
2548
- additional_data=e.additional_data,
2549
- )
2550
- run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2551
- if run_response.content is None:
2547
+ except RunCancelledException as e:
2548
+ # Handle run cancellation
2549
+ log_info(f"Run {run_response.run_id} was cancelled")
2552
2550
  run_response.content = str(e)
2551
+ run_response.status = RunStatus.cancelled
2553
2552
 
2554
- log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
2553
+ # Cleanup and store the run response and session
2554
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2555
2555
 
2556
- await self._acleanup_and_store(run_response=run_response, session=team_session)
2556
+ return run_response
2557
2557
 
2558
- return run_response
2558
+ except (InputCheckError, OutputCheckError) as e:
2559
+ run_response.status = RunStatus.error
2560
+ run_error = create_team_run_error_event(
2561
+ run_response,
2562
+ error=str(e),
2563
+ error_id=e.error_id,
2564
+ error_type=e.type,
2565
+ additional_data=e.additional_data,
2566
+ )
2567
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2568
+ if run_response.content is None:
2569
+ run_response.content = str(e)
2559
2570
 
2560
- except Exception as e:
2561
- if attempt < num_attempts - 1:
2562
- # Calculate delay with exponential backoff if enabled
2563
- if self.exponential_backoff:
2564
- delay = self.delay_between_retries * (2**attempt)
2565
- else:
2566
- delay = self.delay_between_retries
2571
+ log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
2567
2572
 
2568
- log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2569
- time.sleep(delay)
2570
- continue
2573
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2571
2574
 
2572
- run_error = create_team_run_error_event(run_response, error=str(e))
2573
- run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2575
+ return run_response
2574
2576
 
2575
- if run_response.content is None:
2576
- run_response.content = str(e)
2577
+ except Exception as e:
2578
+ if attempt < num_attempts - 1:
2579
+ # Calculate delay with exponential backoff if enabled
2580
+ if self.exponential_backoff:
2581
+ delay = self.delay_between_retries * (2**attempt)
2582
+ else:
2583
+ delay = self.delay_between_retries
2584
+
2585
+ log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2586
+ time.sleep(delay)
2587
+ continue
2577
2588
 
2578
- log_error(f"Error in Team run: {str(e)}")
2589
+ run_error = create_team_run_error_event(run_response, error=str(e))
2590
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2579
2591
 
2580
- # Cleanup and store the run response and session
2581
- await self._acleanup_and_store(run_response=run_response, session=team_session)
2592
+ if run_response.content is None:
2593
+ run_response.content = str(e)
2582
2594
 
2583
- return run_response
2595
+ log_error(f"Error in Team run: {str(e)}")
2584
2596
 
2585
- finally:
2586
- # Always disconnect connectable tools
2587
- self._disconnect_connectable_tools()
2588
- await self._disconnect_mcp_tools()
2589
- # Cancel the memory task if it's still running
2590
- if memory_task is not None and not memory_task.done():
2591
- memory_task.cancel()
2592
- try:
2593
- await memory_task
2594
- except asyncio.CancelledError:
2595
- pass
2597
+ # Cleanup and store the run response and session
2598
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2599
+
2600
+ return run_response
2601
+ finally:
2602
+ # Always disconnect connectable tools
2603
+ self._disconnect_connectable_tools()
2604
+ await self._disconnect_mcp_tools()
2605
+
2606
+ # Cancel background task on error (await_for_open_threads handles waiting on success)
2607
+ if memory_task is not None and not memory_task.done():
2608
+ memory_task.cancel()
2609
+ try:
2610
+ await memory_task
2611
+ except asyncio.CancelledError:
2612
+ pass
2596
2613
 
2597
- # Always clean up the run tracking
2598
- cleanup_run(run_response.run_id) # type: ignore
2614
+ # Always clean up the run tracking
2615
+ await acleanup_run(run_response.run_id) # type: ignore
2599
2616
 
2600
2617
  return run_response
2601
2618
 
@@ -2633,365 +2650,362 @@ class Team:
2633
2650
  12. Create session summary
2634
2651
  13. Cleanup and store (scrub, add to session, calculate metrics, save session)
2635
2652
  """
2653
+ log_debug(f"Team Run Start: {run_response.run_id}", center=True)
2636
2654
 
2637
2655
  memory_task = None
2638
2656
 
2639
- # Set up retry logic
2640
- num_attempts = self.retries + 1
2641
- for attempt in range(num_attempts):
2642
- if num_attempts > 1:
2643
- log_debug(f"Retrying Team run {run_response.run_id}. Attempt {attempt + 1} of {num_attempts}...")
2657
+ try:
2658
+ # Set up retry logic
2659
+ num_attempts = self.retries + 1
2660
+ for attempt in range(num_attempts):
2661
+ if num_attempts > 1:
2662
+ log_debug(f"Retrying Team run {run_response.run_id}. Attempt {attempt + 1} of {num_attempts}...")
2644
2663
 
2645
- try:
2646
- # 1. Resolve dependencies
2647
- if run_context.dependencies is not None:
2648
- await self._aresolve_run_dependencies(run_context=run_context)
2664
+ try:
2665
+ # 1. Resolve dependencies
2666
+ if run_context.dependencies is not None:
2667
+ await self._aresolve_run_dependencies(run_context=run_context)
2649
2668
 
2650
- # 2. Read or create session. Reads from the database if provided.
2651
- if self._has_async_db():
2652
- team_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
2653
- else:
2654
- team_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2669
+ # 2. Read or create session. Reads from the database if provided.
2670
+ if self._has_async_db():
2671
+ team_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
2672
+ else:
2673
+ team_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2655
2674
 
2656
- # 3. Update metadata and session state
2657
- self._update_metadata(session=team_session)
2658
- # Initialize session state
2659
- run_context.session_state = self._initialize_session_state(
2660
- session_state=run_context.session_state if run_context.session_state is not None else {},
2661
- user_id=user_id,
2662
- session_id=session_id,
2663
- run_id=run_response.run_id,
2664
- )
2665
- # Update session state from DB
2666
- if run_context.session_state is not None:
2667
- run_context.session_state = self._load_session_state(
2668
- session=team_session, session_state=run_context.session_state
2669
- ) # type: ignore
2675
+ # 3. Update metadata and session state
2676
+ self._update_metadata(session=team_session)
2677
+ # Initialize session state
2678
+ run_context.session_state = self._initialize_session_state(
2679
+ session_state=run_context.session_state if run_context.session_state is not None else {},
2680
+ user_id=user_id,
2681
+ session_id=session_id,
2682
+ run_id=run_response.run_id,
2683
+ )
2684
+ # Update session state from DB
2685
+ if run_context.session_state is not None:
2686
+ run_context.session_state = self._load_session_state(
2687
+ session=team_session, session_state=run_context.session_state
2688
+ ) # type: ignore
2670
2689
 
2671
- # 4. Execute pre-hooks
2672
- run_input = cast(TeamRunInput, run_response.input)
2673
- self.model = cast(Model, self.model)
2674
- if self.pre_hooks is not None:
2675
- pre_hook_iterator = self._aexecute_pre_hooks(
2676
- hooks=self.pre_hooks, # type: ignore
2690
+ # 4. Execute pre-hooks
2691
+ run_input = cast(TeamRunInput, run_response.input)
2692
+ self.model = cast(Model, self.model)
2693
+ if self.pre_hooks is not None:
2694
+ pre_hook_iterator = self._aexecute_pre_hooks(
2695
+ hooks=self.pre_hooks, # type: ignore
2696
+ run_response=run_response,
2697
+ run_context=run_context,
2698
+ run_input=run_input,
2699
+ session=team_session,
2700
+ user_id=user_id,
2701
+ debug_mode=debug_mode,
2702
+ stream_events=stream_events,
2703
+ background_tasks=background_tasks,
2704
+ **kwargs,
2705
+ )
2706
+ async for pre_hook_event in pre_hook_iterator:
2707
+ yield pre_hook_event
2708
+
2709
+ # 5. Determine tools for model
2710
+ team_run_context: Dict[str, Any] = {}
2711
+ self.model = cast(Model, self.model)
2712
+ await self._check_and_refresh_mcp_tools()
2713
+ _tools = self._determine_tools_for_model(
2714
+ model=self.model,
2677
2715
  run_response=run_response,
2678
2716
  run_context=run_context,
2679
- run_input=run_input,
2680
- session=team_session,
2717
+ team_run_context=team_run_context,
2718
+ session=team_session, # type: ignore
2681
2719
  user_id=user_id,
2720
+ async_mode=True,
2721
+ input_message=run_input.input_content,
2722
+ images=run_input.images,
2723
+ videos=run_input.videos,
2724
+ audio=run_input.audios,
2725
+ files=run_input.files,
2682
2726
  debug_mode=debug_mode,
2727
+ add_history_to_context=add_history_to_context,
2728
+ add_dependencies_to_context=add_dependencies_to_context,
2729
+ add_session_state_to_context=add_session_state_to_context,
2730
+ stream=True,
2683
2731
  stream_events=stream_events,
2684
- background_tasks=background_tasks,
2685
- **kwargs,
2686
2732
  )
2687
- async for pre_hook_event in pre_hook_iterator:
2688
- yield pre_hook_event
2689
-
2690
- # 5. Determine tools for model
2691
- team_run_context: Dict[str, Any] = {}
2692
- self.model = cast(Model, self.model)
2693
- await self._check_and_refresh_mcp_tools()
2694
- _tools = self._determine_tools_for_model(
2695
- model=self.model,
2696
- run_response=run_response,
2697
- run_context=run_context,
2698
- team_run_context=team_run_context,
2699
- session=team_session, # type: ignore
2700
- user_id=user_id,
2701
- async_mode=True,
2702
- input_message=run_input.input_content,
2703
- images=run_input.images,
2704
- videos=run_input.videos,
2705
- audio=run_input.audios,
2706
- files=run_input.files,
2707
- debug_mode=debug_mode,
2708
- add_history_to_context=add_history_to_context,
2709
- add_dependencies_to_context=add_dependencies_to_context,
2710
- add_session_state_to_context=add_session_state_to_context,
2711
- stream=True,
2712
- stream_events=stream_events,
2713
- )
2714
-
2715
- # 6. Prepare run messages
2716
- run_messages = await self._aget_run_messages(
2717
- run_response=run_response,
2718
- run_context=run_context,
2719
- session=team_session, # type: ignore
2720
- user_id=user_id,
2721
- input_message=run_input.input_content,
2722
- audio=run_input.audios,
2723
- images=run_input.images,
2724
- videos=run_input.videos,
2725
- files=run_input.files,
2726
- add_history_to_context=add_history_to_context,
2727
- add_dependencies_to_context=add_dependencies_to_context,
2728
- add_session_state_to_context=add_session_state_to_context,
2729
- tools=_tools,
2730
- **kwargs,
2731
- )
2732
-
2733
- log_debug(f"Team Run Start: {run_response.run_id}", center=True)
2734
-
2735
- # 7. Start memory creation in background task
2736
- memory_task = None
2737
- if (
2738
- run_messages.user_message is not None
2739
- and self.memory_manager is not None
2740
- and self.enable_user_memories
2741
- and not self.enable_agentic_memory
2742
- ):
2743
- log_debug("Starting memory creation in background task.")
2744
- memory_task = asyncio.create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2745
2733
 
2746
- # Considering both stream_events and stream_intermediate_steps (deprecated)
2747
- stream_events = stream_events or stream_intermediate_steps
2734
+ # 6. Prepare run messages
2735
+ run_messages = await self._aget_run_messages(
2736
+ run_response=run_response,
2737
+ run_context=run_context,
2738
+ session=team_session, # type: ignore
2739
+ user_id=user_id,
2740
+ input_message=run_input.input_content,
2741
+ audio=run_input.audios,
2742
+ images=run_input.images,
2743
+ videos=run_input.videos,
2744
+ files=run_input.files,
2745
+ add_history_to_context=add_history_to_context,
2746
+ add_dependencies_to_context=add_dependencies_to_context,
2747
+ add_session_state_to_context=add_session_state_to_context,
2748
+ tools=_tools,
2749
+ **kwargs,
2750
+ )
2748
2751
 
2749
- # Yield the run started event
2750
- if stream_events:
2751
- yield handle_event( # type: ignore
2752
- create_team_run_started_event(from_run_response=run_response),
2753
- run_response,
2754
- events_to_skip=self.events_to_skip,
2755
- store_events=self.store_events,
2752
+ # 7. Start memory creation in background task
2753
+ memory_task = await self._astart_memory_task(
2754
+ run_messages=run_messages,
2755
+ user_id=user_id,
2756
+ existing_task=memory_task,
2756
2757
  )
2757
2758
 
2758
- # 8. Reason about the task if reasoning is enabled
2759
- async for item in self._ahandle_reasoning_stream(
2760
- run_response=run_response,
2761
- run_messages=run_messages,
2762
- stream_events=stream_events,
2763
- ):
2764
- raise_if_cancelled(run_response.run_id) # type: ignore
2765
- yield item
2759
+ # Considering both stream_events and stream_intermediate_steps (deprecated)
2760
+ stream_events = stream_events or stream_intermediate_steps
2766
2761
 
2767
- # Check for cancellation before model processing
2768
- raise_if_cancelled(run_response.run_id) # type: ignore
2762
+ # Yield the run started event
2763
+ if stream_events:
2764
+ yield handle_event( # type: ignore
2765
+ create_team_run_started_event(from_run_response=run_response),
2766
+ run_response,
2767
+ events_to_skip=self.events_to_skip,
2768
+ store_events=self.store_events,
2769
+ )
2769
2770
 
2770
- # 9. Get a response from the model
2771
- if self.output_model is None:
2772
- async for event in self._ahandle_model_response_stream(
2773
- session=team_session,
2774
- run_response=run_response,
2775
- run_messages=run_messages,
2776
- tools=_tools,
2777
- response_format=response_format,
2778
- stream_events=stream_events,
2779
- session_state=run_context.session_state,
2780
- run_context=run_context,
2781
- ):
2782
- raise_if_cancelled(run_response.run_id) # type: ignore
2783
- yield event
2784
- else:
2785
- async for event in self._ahandle_model_response_stream(
2786
- session=team_session,
2771
+ # 8. Reason about the task if reasoning is enabled
2772
+ async for item in self._ahandle_reasoning_stream(
2787
2773
  run_response=run_response,
2788
2774
  run_messages=run_messages,
2789
- tools=_tools,
2790
- response_format=response_format,
2791
2775
  stream_events=stream_events,
2792
- session_state=run_context.session_state,
2793
- run_context=run_context,
2794
2776
  ):
2795
- raise_if_cancelled(run_response.run_id) # type: ignore
2796
- from agno.run.team import IntermediateRunContentEvent, RunContentEvent
2797
-
2798
- if isinstance(event, RunContentEvent):
2799
- if stream_events:
2800
- yield IntermediateRunContentEvent(
2801
- content=event.content,
2802
- content_type=event.content_type,
2803
- )
2804
- else:
2805
- yield event
2777
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2778
+ yield item
2806
2779
 
2807
- async for event in self._agenerate_response_with_output_model_stream(
2808
- session=team_session,
2809
- run_response=run_response,
2810
- run_messages=run_messages,
2811
- stream_events=stream_events,
2812
- ):
2813
- raise_if_cancelled(run_response.run_id) # type: ignore
2814
- yield event
2780
+ # Check for cancellation before model processing
2781
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2815
2782
 
2816
- # Check for cancellation after model processing
2817
- raise_if_cancelled(run_response.run_id) # type: ignore
2783
+ # 9. Get a response from the model
2784
+ if self.output_model is None:
2785
+ async for event in self._ahandle_model_response_stream(
2786
+ session=team_session,
2787
+ run_response=run_response,
2788
+ run_messages=run_messages,
2789
+ tools=_tools,
2790
+ response_format=response_format,
2791
+ stream_events=stream_events,
2792
+ session_state=run_context.session_state,
2793
+ run_context=run_context,
2794
+ ):
2795
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2796
+ yield event
2797
+ else:
2798
+ async for event in self._ahandle_model_response_stream(
2799
+ session=team_session,
2800
+ run_response=run_response,
2801
+ run_messages=run_messages,
2802
+ tools=_tools,
2803
+ response_format=response_format,
2804
+ stream_events=stream_events,
2805
+ session_state=run_context.session_state,
2806
+ run_context=run_context,
2807
+ ):
2808
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2809
+ from agno.run.team import IntermediateRunContentEvent, RunContentEvent
2810
+
2811
+ if isinstance(event, RunContentEvent):
2812
+ if stream_events:
2813
+ yield IntermediateRunContentEvent(
2814
+ content=event.content,
2815
+ content_type=event.content_type,
2816
+ )
2817
+ else:
2818
+ yield event
2818
2819
 
2819
- # 10. Parse response with parser model if provided
2820
- async for event in self._aparse_response_with_parser_model_stream(
2821
- session=team_session,
2822
- run_response=run_response,
2823
- stream_events=stream_events,
2824
- run_context=run_context,
2825
- ):
2826
- yield event
2820
+ async for event in self._agenerate_response_with_output_model_stream(
2821
+ session=team_session,
2822
+ run_response=run_response,
2823
+ run_messages=run_messages,
2824
+ stream_events=stream_events,
2825
+ ):
2826
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2827
+ yield event
2827
2828
 
2828
- # Yield RunContentCompletedEvent
2829
- if stream_events:
2830
- yield handle_event( # type: ignore
2831
- create_team_run_content_completed_event(from_run_response=run_response),
2832
- run_response,
2833
- events_to_skip=self.events_to_skip,
2834
- store_events=self.store_events,
2835
- )
2829
+ # Check for cancellation after model processing
2830
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2836
2831
 
2837
- # Execute post-hooks after output is generated but before response is returned
2838
- if self.post_hooks is not None:
2839
- async for event in self._aexecute_post_hooks(
2840
- hooks=self.post_hooks, # type: ignore
2841
- run_output=run_response,
2842
- run_context=run_context,
2832
+ # 10. Parse response with parser model if provided
2833
+ async for event in self._aparse_response_with_parser_model_stream(
2843
2834
  session=team_session,
2844
- user_id=user_id,
2845
- debug_mode=debug_mode,
2835
+ run_response=run_response,
2846
2836
  stream_events=stream_events,
2847
- background_tasks=background_tasks,
2848
- **kwargs,
2837
+ run_context=run_context,
2849
2838
  ):
2850
2839
  yield event
2851
2840
 
2852
- raise_if_cancelled(run_response.run_id) # type: ignore
2853
- # 11. Wait for background memory creation
2854
- async for event in await_for_thread_tasks_stream(
2855
- run_response=run_response,
2856
- memory_task=memory_task,
2857
- stream_events=stream_events,
2858
- events_to_skip=self.events_to_skip, # type: ignore
2859
- store_events=self.store_events,
2860
- ):
2861
- yield event
2862
-
2863
- raise_if_cancelled(run_response.run_id) # type: ignore
2864
-
2865
- # 12. Create session summary
2866
- if self.session_summary_manager is not None:
2867
- # Upsert the RunOutput to Team Session before creating the session summary
2868
- team_session.upsert_run(run_response=run_response)
2869
-
2870
- if stream_events:
2871
- yield handle_event( # type: ignore
2872
- create_team_session_summary_started_event(from_run_response=run_response),
2873
- run_response,
2874
- events_to_skip=self.events_to_skip,
2875
- store_events=self.store_events,
2876
- )
2877
- try:
2878
- await self.session_summary_manager.acreate_session_summary(session=team_session)
2879
- except Exception as e:
2880
- log_warning(f"Error in session summary creation: {str(e)}")
2841
+ # Yield RunContentCompletedEvent
2881
2842
  if stream_events:
2882
2843
  yield handle_event( # type: ignore
2883
- create_team_session_summary_completed_event(
2884
- from_run_response=run_response, session_summary=team_session.summary
2885
- ),
2844
+ create_team_run_content_completed_event(from_run_response=run_response),
2886
2845
  run_response,
2887
2846
  events_to_skip=self.events_to_skip,
2888
2847
  store_events=self.store_events,
2889
2848
  )
2890
2849
 
2891
- raise_if_cancelled(run_response.run_id) # type: ignore
2850
+ # Execute post-hooks after output is generated but before response is returned
2851
+ if self.post_hooks is not None:
2852
+ async for event in self._aexecute_post_hooks(
2853
+ hooks=self.post_hooks, # type: ignore
2854
+ run_output=run_response,
2855
+ run_context=run_context,
2856
+ session=team_session,
2857
+ user_id=user_id,
2858
+ debug_mode=debug_mode,
2859
+ stream_events=stream_events,
2860
+ background_tasks=background_tasks,
2861
+ **kwargs,
2862
+ ):
2863
+ yield event
2892
2864
 
2893
- # Create the run completed event
2894
- completed_event = handle_event(
2895
- create_team_run_completed_event(from_run_response=run_response),
2896
- run_response,
2897
- events_to_skip=self.events_to_skip,
2898
- store_events=self.store_events,
2899
- )
2865
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2866
+ # 11. Wait for background memory creation
2867
+ async for event in await_for_thread_tasks_stream(
2868
+ run_response=run_response,
2869
+ memory_task=memory_task,
2870
+ stream_events=stream_events,
2871
+ events_to_skip=self.events_to_skip, # type: ignore
2872
+ store_events=self.store_events,
2873
+ ):
2874
+ yield event
2900
2875
 
2901
- # Set the run status to completed
2902
- run_response.status = RunStatus.completed
2876
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2903
2877
 
2904
- # 13. Cleanup and store the run response and session
2905
- await self._acleanup_and_store(run_response=run_response, session=team_session)
2878
+ # 12. Create session summary
2879
+ if self.session_summary_manager is not None:
2880
+ # Upsert the RunOutput to Team Session before creating the session summary
2881
+ team_session.upsert_run(run_response=run_response)
2906
2882
 
2907
- if stream_events:
2908
- yield completed_event
2883
+ if stream_events:
2884
+ yield handle_event( # type: ignore
2885
+ create_team_session_summary_started_event(from_run_response=run_response),
2886
+ run_response,
2887
+ events_to_skip=self.events_to_skip,
2888
+ store_events=self.store_events,
2889
+ )
2890
+ try:
2891
+ await self.session_summary_manager.acreate_session_summary(session=team_session)
2892
+ except Exception as e:
2893
+ log_warning(f"Error in session summary creation: {str(e)}")
2894
+ if stream_events:
2895
+ yield handle_event( # type: ignore
2896
+ create_team_session_summary_completed_event(
2897
+ from_run_response=run_response, session_summary=team_session.summary
2898
+ ),
2899
+ run_response,
2900
+ events_to_skip=self.events_to_skip,
2901
+ store_events=self.store_events,
2902
+ )
2909
2903
 
2910
- if yield_run_output:
2911
- yield run_response
2904
+ await araise_if_cancelled(run_response.run_id) # type: ignore
2905
+
2906
+ # Create the run completed event
2907
+ completed_event = handle_event(
2908
+ create_team_run_completed_event(from_run_response=run_response),
2909
+ run_response,
2910
+ events_to_skip=self.events_to_skip,
2911
+ store_events=self.store_events,
2912
+ )
2912
2913
 
2913
- # Log Team Telemetry
2914
- await self._alog_team_telemetry(session_id=team_session.session_id, run_id=run_response.run_id)
2914
+ # Set the run status to completed
2915
+ run_response.status = RunStatus.completed
2915
2916
 
2916
- log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
2917
+ # 13. Cleanup and store the run response and session
2918
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2917
2919
 
2918
- except RunCancelledException as e:
2919
- # Handle run cancellation during async streaming
2920
- log_info(f"Team run {run_response.run_id} was cancelled during async streaming")
2921
- run_response.status = RunStatus.cancelled
2922
- run_response.content = str(e)
2920
+ if stream_events:
2921
+ yield completed_event
2923
2922
 
2924
- # Yield the cancellation event
2925
- yield handle_event( # type: ignore
2926
- create_team_run_cancelled_event(from_run_response=run_response, reason=str(e)),
2927
- run_response,
2928
- events_to_skip=self.events_to_skip,
2929
- store_events=self.store_events,
2930
- )
2923
+ if yield_run_output:
2924
+ yield run_response
2931
2925
 
2932
- # Cleanup and store the run response and session
2933
- await self._acleanup_and_store(run_response=run_response, session=team_session)
2926
+ # Log Team Telemetry
2927
+ await self._alog_team_telemetry(session_id=team_session.session_id, run_id=run_response.run_id)
2934
2928
 
2935
- except (InputCheckError, OutputCheckError) as e:
2936
- run_response.status = RunStatus.error
2937
- run_error = create_team_run_error_event(
2938
- run_response,
2939
- error=str(e),
2940
- error_id=e.error_id,
2941
- error_type=e.type,
2942
- additional_data=e.additional_data,
2943
- )
2944
- run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2945
- if run_response.content is None:
2929
+ log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
2930
+ break
2931
+ except RunCancelledException as e:
2932
+ # Handle run cancellation during async streaming
2933
+ log_info(f"Team run {run_response.run_id} was cancelled during async streaming")
2934
+ run_response.status = RunStatus.cancelled
2946
2935
  run_response.content = str(e)
2947
2936
 
2948
- log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
2937
+ # Yield the cancellation event
2938
+ yield handle_event( # type: ignore
2939
+ create_team_run_cancelled_event(from_run_response=run_response, reason=str(e)),
2940
+ run_response,
2941
+ events_to_skip=self.events_to_skip,
2942
+ store_events=self.store_events,
2943
+ )
2944
+
2945
+ # Cleanup and store the run response and session
2946
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2949
2947
 
2950
- await self._acleanup_and_store(run_response=run_response, session=team_session)
2948
+ except (InputCheckError, OutputCheckError) as e:
2949
+ run_response.status = RunStatus.error
2950
+ run_error = create_team_run_error_event(
2951
+ run_response,
2952
+ error=str(e),
2953
+ error_id=e.error_id,
2954
+ error_type=e.type,
2955
+ additional_data=e.additional_data,
2956
+ )
2957
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2958
+ if run_response.content is None:
2959
+ run_response.content = str(e)
2951
2960
 
2952
- yield run_error
2961
+ log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
2953
2962
 
2954
- break
2963
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2955
2964
 
2956
- except Exception as e:
2957
- if attempt < num_attempts - 1:
2958
- # Calculate delay with exponential backoff if enabled
2959
- if self.exponential_backoff:
2960
- delay = self.delay_between_retries * (2**attempt)
2961
- else:
2962
- delay = self.delay_between_retries
2965
+ yield run_error
2963
2966
 
2964
- log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2965
- time.sleep(delay)
2966
- continue
2967
+ break
2967
2968
 
2968
- run_response.status = RunStatus.error
2969
- run_error = create_team_run_error_event(run_response, error=str(e))
2970
- run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2971
- if run_response.content is None:
2972
- run_response.content = str(e)
2969
+ except Exception as e:
2970
+ if attempt < num_attempts - 1:
2971
+ # Calculate delay with exponential backoff if enabled
2972
+ if self.exponential_backoff:
2973
+ delay = self.delay_between_retries * (2**attempt)
2974
+ else:
2975
+ delay = self.delay_between_retries
2976
+
2977
+ log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2978
+ time.sleep(delay)
2979
+ continue
2973
2980
 
2974
- log_error(f"Error in Team run: {str(e)}")
2981
+ run_response.status = RunStatus.error
2982
+ run_error = create_team_run_error_event(run_response, error=str(e))
2983
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2984
+ if run_response.content is None:
2985
+ run_response.content = str(e)
2975
2986
 
2976
- # Cleanup and store the run response and session
2977
- await self._acleanup_and_store(run_response=run_response, session=team_session)
2987
+ log_error(f"Error in Team run: {str(e)}")
2978
2988
 
2979
- yield run_error
2989
+ # Cleanup and store the run response and session
2990
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2980
2991
 
2981
- finally:
2982
- # Always disconnect connectable tools
2983
- self._disconnect_connectable_tools()
2984
- await self._disconnect_mcp_tools()
2985
- # Cancel the memory task if it's still running
2986
- if memory_task is not None and not memory_task.done():
2987
- memory_task.cancel()
2988
- try:
2989
- await memory_task
2990
- except asyncio.CancelledError:
2991
- pass
2992
+ yield run_error
2992
2993
 
2993
- # Always clean up the run tracking
2994
- cleanup_run(run_response.run_id) # type: ignore
2994
+ finally:
2995
+ # Always disconnect connectable tools
2996
+ self._disconnect_connectable_tools()
2997
+ await self._disconnect_mcp_tools()
2998
+
2999
+ # Cancel background task on error (await_for_thread_tasks_stream handles waiting on success)
3000
+ if memory_task is not None and not memory_task.done():
3001
+ memory_task.cancel()
3002
+ try:
3003
+ await memory_task
3004
+ except asyncio.CancelledError:
3005
+ pass
3006
+
3007
+ # Always clean up the run tracking
3008
+ await acleanup_run(run_response.run_id) # type: ignore
2995
3009
 
2996
3010
  @overload
2997
3011
  async def arun(
@@ -3083,7 +3097,6 @@ class Team:
3083
3097
 
3084
3098
  # Set the id for the run and register it immediately for cancellation tracking
3085
3099
  run_id = run_id or str(uuid4())
3086
- register_run(run_id)
3087
3100
 
3088
3101
  if (add_history_to_context or self.add_history_to_context) and not self.db and not self.parent_team_id:
3089
3102
  log_warning(
@@ -3982,6 +3995,74 @@ class Team:
3982
3995
  team_id=self.id,
3983
3996
  )
3984
3997
 
3998
+ async def _astart_memory_task(
3999
+ self,
4000
+ run_messages: RunMessages,
4001
+ user_id: Optional[str],
4002
+ existing_task: Optional[asyncio.Task[None]],
4003
+ ) -> Optional[asyncio.Task[None]]:
4004
+ """Cancel any existing memory task and start a new one if conditions are met.
4005
+
4006
+ Args:
4007
+ run_messages: The run messages containing the user message.
4008
+ user_id: The user ID for memory creation.
4009
+ existing_task: An existing memory task to cancel before starting a new one.
4010
+
4011
+ Returns:
4012
+ A new memory task if conditions are met, None otherwise.
4013
+ """
4014
+ # Cancel any existing task from a previous retry attempt
4015
+ if existing_task is not None and not existing_task.done():
4016
+ existing_task.cancel()
4017
+ try:
4018
+ await existing_task
4019
+ except asyncio.CancelledError:
4020
+ pass
4021
+
4022
+ # Create new task if conditions are met
4023
+ if (
4024
+ run_messages.user_message is not None
4025
+ and self.memory_manager is not None
4026
+ and self.enable_user_memories
4027
+ and not self.enable_agentic_memory
4028
+ ):
4029
+ log_debug("Starting memory creation in background task.")
4030
+ return asyncio.create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
4031
+
4032
+ return None
4033
+
4034
+ def _start_memory_future(
4035
+ self,
4036
+ run_messages: RunMessages,
4037
+ user_id: Optional[str],
4038
+ existing_future: Optional[Future[None]],
4039
+ ) -> Optional[Future[None]]:
4040
+ """Cancel any existing memory future and start a new one if conditions are met.
4041
+
4042
+ Args:
4043
+ run_messages: The run messages containing the user message.
4044
+ user_id: The user ID for memory creation.
4045
+ existing_future: An existing memory future to cancel before starting a new one.
4046
+
4047
+ Returns:
4048
+ A new memory future if conditions are met, None otherwise.
4049
+ """
4050
+ # Cancel any existing future from a previous retry attempt
4051
+ if existing_future is not None and not existing_future.done():
4052
+ existing_future.cancel()
4053
+
4054
+ # Create new future if conditions are met
4055
+ if (
4056
+ run_messages.user_message is not None
4057
+ and self.memory_manager is not None
4058
+ and self.enable_user_memories
4059
+ and not self.enable_agentic_memory
4060
+ ):
4061
+ log_debug("Starting memory creation in background thread.")
4062
+ return self.background_executor.submit(self._make_memories, run_messages=run_messages, user_id=user_id)
4063
+
4064
+ return None
4065
+
3985
4066
  def _get_response_format(
3986
4067
  self, model: Optional[Model] = None, run_context: Optional[RunContext] = None
3987
4068
  ) -> Optional[Union[Dict, Type[BaseModel]]]: