agno 2.1.10__py3-none-any.whl → 2.2.1__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 (45) hide show
  1. agno/agent/agent.py +1594 -1248
  2. agno/knowledge/knowledge.py +11 -0
  3. agno/knowledge/reader/pptx_reader.py +101 -0
  4. agno/knowledge/reader/reader_factory.py +14 -0
  5. agno/knowledge/types.py +1 -0
  6. agno/models/anthropic/claude.py +2 -2
  7. agno/models/base.py +4 -4
  8. agno/models/ollama/chat.py +7 -2
  9. agno/os/app.py +1 -1
  10. agno/os/interfaces/a2a/router.py +2 -2
  11. agno/os/interfaces/agui/router.py +2 -2
  12. agno/os/router.py +7 -7
  13. agno/os/routers/evals/schemas.py +31 -31
  14. agno/os/routers/health.py +6 -2
  15. agno/os/routers/knowledge/schemas.py +49 -47
  16. agno/os/routers/memory/schemas.py +16 -16
  17. agno/os/routers/metrics/schemas.py +16 -16
  18. agno/os/routers/session/session.py +382 -7
  19. agno/os/schema.py +254 -231
  20. agno/os/utils.py +1 -1
  21. agno/run/agent.py +54 -1
  22. agno/run/team.py +48 -0
  23. agno/run/workflow.py +15 -5
  24. agno/session/summary.py +45 -13
  25. agno/session/team.py +90 -5
  26. agno/team/team.py +1130 -849
  27. agno/utils/agent.py +372 -0
  28. agno/utils/events.py +144 -2
  29. agno/utils/message.py +60 -0
  30. agno/utils/print_response/agent.py +10 -6
  31. agno/utils/print_response/team.py +6 -4
  32. agno/utils/print_response/workflow.py +7 -5
  33. agno/utils/team.py +9 -8
  34. agno/workflow/condition.py +17 -9
  35. agno/workflow/loop.py +18 -10
  36. agno/workflow/parallel.py +14 -6
  37. agno/workflow/router.py +16 -8
  38. agno/workflow/step.py +14 -6
  39. agno/workflow/steps.py +14 -6
  40. agno/workflow/workflow.py +331 -123
  41. {agno-2.1.10.dist-info → agno-2.2.1.dist-info}/METADATA +63 -23
  42. {agno-2.1.10.dist-info → agno-2.2.1.dist-info}/RECORD +45 -43
  43. {agno-2.1.10.dist-info → agno-2.2.1.dist-info}/WHEEL +0 -0
  44. {agno-2.1.10.dist-info → agno-2.2.1.dist-info}/licenses/LICENSE +0 -0
  45. {agno-2.1.10.dist-info → agno-2.2.1.dist-info}/top_level.txt +0 -0
agno/os/utils.py CHANGED
@@ -158,7 +158,7 @@ def extract_input_media(run_dict: Dict[str, Any]) -> Dict[str, Any]:
158
158
  "files": [],
159
159
  }
160
160
 
161
- input = run_dict.get("input", [])
161
+ input = run_dict.get("input", {})
162
162
  input_media["images"].extend(input.get("images", []))
163
163
  input_media["videos"].extend(input.get("videos", []))
164
164
  input_media["audios"].extend(input.get("audios", []))
agno/run/agent.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from dataclasses import asdict, dataclass, field
2
2
  from enum import Enum
3
3
  from time import time
4
- from typing import Any, Dict, List, Optional, Sequence, Union
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union
5
5
 
6
6
  from pydantic import BaseModel
7
7
 
@@ -13,6 +13,9 @@ from agno.reasoning.step import ReasoningStep
13
13
  from agno.run.base import BaseRunOutputEvent, MessageReferences, RunStatus
14
14
  from agno.utils.log import logger
15
15
 
16
+ if TYPE_CHECKING:
17
+ from agno.session.summary import SessionSummary
18
+
16
19
 
17
20
  @dataclass
18
21
  class RunInput:
@@ -109,6 +112,7 @@ class RunEvent(str, Enum):
109
112
 
110
113
  run_started = "RunStarted"
111
114
  run_content = "RunContent"
115
+ run_content_completed = "RunContentCompleted"
112
116
  run_intermediate_content = "RunIntermediateContent"
113
117
  run_completed = "RunCompleted"
114
118
  run_error = "RunError"
@@ -120,6 +124,9 @@ class RunEvent(str, Enum):
120
124
  pre_hook_started = "PreHookStarted"
121
125
  pre_hook_completed = "PreHookCompleted"
122
126
 
127
+ post_hook_started = "PostHookStarted"
128
+ post_hook_completed = "PostHookCompleted"
129
+
123
130
  tool_call_started = "ToolCallStarted"
124
131
  tool_call_completed = "ToolCallCompleted"
125
132
 
@@ -130,6 +137,9 @@ class RunEvent(str, Enum):
130
137
  memory_update_started = "MemoryUpdateStarted"
131
138
  memory_update_completed = "MemoryUpdateCompleted"
132
139
 
140
+ session_summary_started = "SessionSummaryStarted"
141
+ session_summary_completed = "SessionSummaryCompleted"
142
+
133
143
  parser_model_response_started = "ParserModelResponseStarted"
134
144
  parser_model_response_completed = "ParserModelResponseCompleted"
135
145
 
@@ -200,6 +210,11 @@ class RunContentEvent(BaseAgentRunEvent):
200
210
  reasoning_messages: Optional[List[Message]] = None
201
211
 
202
212
 
213
+ @dataclass
214
+ class RunContentCompletedEvent(BaseAgentRunEvent):
215
+ event: str = RunEvent.run_content_completed.value
216
+
217
+
203
218
  @dataclass
204
219
  class IntermediateRunContentEvent(BaseAgentRunEvent):
205
220
  event: str = RunEvent.run_intermediate_content.value
@@ -277,6 +292,18 @@ class PreHookCompletedEvent(BaseAgentRunEvent):
277
292
  run_input: Optional[RunInput] = None
278
293
 
279
294
 
295
+ @dataclass
296
+ class PostHookStartedEvent(BaseAgentRunEvent):
297
+ event: str = RunEvent.post_hook_started.value
298
+ post_hook_name: Optional[str] = None
299
+
300
+
301
+ @dataclass
302
+ class PostHookCompletedEvent(BaseAgentRunEvent):
303
+ event: str = RunEvent.post_hook_completed.value
304
+ post_hook_name: Optional[str] = None
305
+
306
+
280
307
  @dataclass
281
308
  class MemoryUpdateStartedEvent(BaseAgentRunEvent):
282
309
  event: str = RunEvent.memory_update_started.value
@@ -287,6 +314,17 @@ class MemoryUpdateCompletedEvent(BaseAgentRunEvent):
287
314
  event: str = RunEvent.memory_update_completed.value
288
315
 
289
316
 
317
+ @dataclass
318
+ class SessionSummaryStartedEvent(BaseAgentRunEvent):
319
+ event: str = RunEvent.session_summary_started.value
320
+
321
+
322
+ @dataclass
323
+ class SessionSummaryCompletedEvent(BaseAgentRunEvent):
324
+ event: str = RunEvent.session_summary_completed.value
325
+ session_summary: Optional["SessionSummary"] = None
326
+
327
+
290
328
  @dataclass
291
329
  class ReasoningStartedEvent(BaseAgentRunEvent):
292
330
  event: str = RunEvent.reasoning_started.value
@@ -347,11 +385,17 @@ class OutputModelResponseCompletedEvent(BaseAgentRunEvent):
347
385
  class CustomEvent(BaseAgentRunEvent):
348
386
  event: str = RunEvent.custom_event.value
349
387
 
388
+ def __init__(self, **kwargs):
389
+ # Store arbitrary attributes directly on the instance
390
+ for key, value in kwargs.items():
391
+ setattr(self, key, value)
392
+
350
393
 
351
394
  RunOutputEvent = Union[
352
395
  RunStartedEvent,
353
396
  RunContentEvent,
354
397
  IntermediateRunContentEvent,
398
+ RunContentCompletedEvent,
355
399
  RunCompletedEvent,
356
400
  RunErrorEvent,
357
401
  RunCancelledEvent,
@@ -359,11 +403,15 @@ RunOutputEvent = Union[
359
403
  RunContinuedEvent,
360
404
  PreHookStartedEvent,
361
405
  PreHookCompletedEvent,
406
+ PostHookStartedEvent,
407
+ PostHookCompletedEvent,
362
408
  ReasoningStartedEvent,
363
409
  ReasoningStepEvent,
364
410
  ReasoningCompletedEvent,
365
411
  MemoryUpdateStartedEvent,
366
412
  MemoryUpdateCompletedEvent,
413
+ SessionSummaryStartedEvent,
414
+ SessionSummaryCompletedEvent,
367
415
  ToolCallStartedEvent,
368
416
  ToolCallCompletedEvent,
369
417
  ParserModelResponseStartedEvent,
@@ -378,6 +426,7 @@ RunOutputEvent = Union[
378
426
  RUN_EVENT_TYPE_REGISTRY = {
379
427
  RunEvent.run_started.value: RunStartedEvent,
380
428
  RunEvent.run_content.value: RunContentEvent,
429
+ RunEvent.run_content_completed.value: RunContentCompletedEvent,
381
430
  RunEvent.run_intermediate_content.value: IntermediateRunContentEvent,
382
431
  RunEvent.run_completed.value: RunCompletedEvent,
383
432
  RunEvent.run_error.value: RunErrorEvent,
@@ -386,11 +435,15 @@ RUN_EVENT_TYPE_REGISTRY = {
386
435
  RunEvent.run_continued.value: RunContinuedEvent,
387
436
  RunEvent.pre_hook_started.value: PreHookStartedEvent,
388
437
  RunEvent.pre_hook_completed.value: PreHookCompletedEvent,
438
+ RunEvent.post_hook_started.value: PostHookStartedEvent,
439
+ RunEvent.post_hook_completed.value: PostHookCompletedEvent,
389
440
  RunEvent.reasoning_started.value: ReasoningStartedEvent,
390
441
  RunEvent.reasoning_step.value: ReasoningStepEvent,
391
442
  RunEvent.reasoning_completed.value: ReasoningCompletedEvent,
392
443
  RunEvent.memory_update_started.value: MemoryUpdateStartedEvent,
393
444
  RunEvent.memory_update_completed.value: MemoryUpdateCompletedEvent,
445
+ RunEvent.session_summary_started.value: SessionSummaryStartedEvent,
446
+ RunEvent.session_summary_completed.value: SessionSummaryCompletedEvent,
394
447
  RunEvent.tool_call_started.value: ToolCallStartedEvent,
395
448
  RunEvent.tool_call_completed.value: ToolCallCompletedEvent,
396
449
  RunEvent.parser_model_response_started.value: ParserModelResponseStartedEvent,
agno/run/team.py CHANGED
@@ -109,6 +109,7 @@ class TeamRunEvent(str, Enum):
109
109
  run_started = "TeamRunStarted"
110
110
  run_content = "TeamRunContent"
111
111
  run_intermediate_content = "TeamRunIntermediateContent"
112
+ run_content_completed = "TeamRunContentCompleted"
112
113
  run_completed = "TeamRunCompleted"
113
114
  run_error = "TeamRunError"
114
115
  run_cancelled = "TeamRunCancelled"
@@ -116,6 +117,9 @@ class TeamRunEvent(str, Enum):
116
117
  pre_hook_started = "TeamPreHookStarted"
117
118
  pre_hook_completed = "TeamPreHookCompleted"
118
119
 
120
+ post_hook_started = "TeamPostHookStarted"
121
+ post_hook_completed = "TeamPostHookCompleted"
122
+
119
123
  tool_call_started = "TeamToolCallStarted"
120
124
  tool_call_completed = "TeamToolCallCompleted"
121
125
 
@@ -126,6 +130,9 @@ class TeamRunEvent(str, Enum):
126
130
  memory_update_started = "TeamMemoryUpdateStarted"
127
131
  memory_update_completed = "TeamMemoryUpdateCompleted"
128
132
 
133
+ session_summary_started = "TeamSessionSummaryStarted"
134
+ session_summary_completed = "TeamSessionSummaryCompleted"
135
+
129
136
  parser_model_response_started = "TeamParserModelResponseStarted"
130
137
  parser_model_response_completed = "TeamParserModelResponseCompleted"
131
138
 
@@ -207,6 +214,11 @@ class IntermediateRunContentEvent(BaseTeamRunEvent):
207
214
  content_type: str = "str"
208
215
 
209
216
 
217
+ @dataclass
218
+ class RunContentCompletedEvent(BaseTeamRunEvent):
219
+ event: str = TeamRunEvent.run_content_completed.value
220
+
221
+
210
222
  @dataclass
211
223
  class RunCompletedEvent(BaseTeamRunEvent):
212
224
  event: str = TeamRunEvent.run_completed.value
@@ -263,6 +275,18 @@ class PreHookCompletedEvent(BaseTeamRunEvent):
263
275
  run_input: Optional[TeamRunInput] = None
264
276
 
265
277
 
278
+ @dataclass
279
+ class PostHookStartedEvent(BaseTeamRunEvent):
280
+ event: str = TeamRunEvent.post_hook_started.value
281
+ post_hook_name: Optional[str] = None
282
+
283
+
284
+ @dataclass
285
+ class PostHookCompletedEvent(BaseTeamRunEvent):
286
+ event: str = TeamRunEvent.post_hook_completed.value
287
+ post_hook_name: Optional[str] = None
288
+
289
+
266
290
  @dataclass
267
291
  class MemoryUpdateStartedEvent(BaseTeamRunEvent):
268
292
  event: str = TeamRunEvent.memory_update_started.value
@@ -273,6 +297,17 @@ class MemoryUpdateCompletedEvent(BaseTeamRunEvent):
273
297
  event: str = TeamRunEvent.memory_update_completed.value
274
298
 
275
299
 
300
+ @dataclass
301
+ class SessionSummaryStartedEvent(BaseTeamRunEvent):
302
+ event: str = TeamRunEvent.session_summary_started.value
303
+
304
+
305
+ @dataclass
306
+ class SessionSummaryCompletedEvent(BaseTeamRunEvent):
307
+ event: str = TeamRunEvent.session_summary_completed.value
308
+ session_summary: Optional[Any] = None
309
+
310
+
276
311
  @dataclass
277
312
  class ReasoningStartedEvent(BaseTeamRunEvent):
278
313
  event: str = TeamRunEvent.reasoning_started.value
@@ -333,11 +368,17 @@ class OutputModelResponseCompletedEvent(BaseTeamRunEvent):
333
368
  class CustomEvent(BaseTeamRunEvent):
334
369
  event: str = TeamRunEvent.custom_event.value
335
370
 
371
+ def __init__(self, **kwargs):
372
+ # Store arbitrary attributes directly on the instance
373
+ for key, value in kwargs.items():
374
+ setattr(self, key, value)
375
+
336
376
 
337
377
  TeamRunOutputEvent = Union[
338
378
  RunStartedEvent,
339
379
  RunContentEvent,
340
380
  IntermediateRunContentEvent,
381
+ RunContentCompletedEvent,
341
382
  RunCompletedEvent,
342
383
  RunErrorEvent,
343
384
  RunCancelledEvent,
@@ -348,6 +389,8 @@ TeamRunOutputEvent = Union[
348
389
  ReasoningCompletedEvent,
349
390
  MemoryUpdateStartedEvent,
350
391
  MemoryUpdateCompletedEvent,
392
+ SessionSummaryStartedEvent,
393
+ SessionSummaryCompletedEvent,
351
394
  ToolCallStartedEvent,
352
395
  ToolCallCompletedEvent,
353
396
  ParserModelResponseStartedEvent,
@@ -362,16 +405,21 @@ TEAM_RUN_EVENT_TYPE_REGISTRY = {
362
405
  TeamRunEvent.run_started.value: RunStartedEvent,
363
406
  TeamRunEvent.run_content.value: RunContentEvent,
364
407
  TeamRunEvent.run_intermediate_content.value: IntermediateRunContentEvent,
408
+ TeamRunEvent.run_content_completed.value: RunContentCompletedEvent,
365
409
  TeamRunEvent.run_completed.value: RunCompletedEvent,
366
410
  TeamRunEvent.run_error.value: RunErrorEvent,
367
411
  TeamRunEvent.run_cancelled.value: RunCancelledEvent,
368
412
  TeamRunEvent.pre_hook_started.value: PreHookStartedEvent,
369
413
  TeamRunEvent.pre_hook_completed.value: PreHookCompletedEvent,
414
+ TeamRunEvent.post_hook_started.value: PostHookStartedEvent,
415
+ TeamRunEvent.post_hook_completed.value: PostHookCompletedEvent,
370
416
  TeamRunEvent.reasoning_started.value: ReasoningStartedEvent,
371
417
  TeamRunEvent.reasoning_step.value: ReasoningStepEvent,
372
418
  TeamRunEvent.reasoning_completed.value: ReasoningCompletedEvent,
373
419
  TeamRunEvent.memory_update_started.value: MemoryUpdateStartedEvent,
374
420
  TeamRunEvent.memory_update_completed.value: MemoryUpdateCompletedEvent,
421
+ TeamRunEvent.session_summary_started.value: SessionSummaryStartedEvent,
422
+ TeamRunEvent.session_summary_completed.value: SessionSummaryCompletedEvent,
375
423
  TeamRunEvent.tool_call_started.value: ToolCallStartedEvent,
376
424
  TeamRunEvent.tool_call_completed.value: ToolCallCompletedEvent,
377
425
  TeamRunEvent.parser_model_response_started.value: ParserModelResponseStartedEvent,
agno/run/workflow.py CHANGED
@@ -6,9 +6,9 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
6
6
  from pydantic import BaseModel
7
7
 
8
8
  from agno.media import Audio, Image, Video
9
- from agno.run.agent import RunOutput
9
+ from agno.run.agent import RunEvent, RunOutput, run_output_event_from_dict
10
10
  from agno.run.base import BaseRunOutputEvent, RunStatus
11
- from agno.run.team import TeamRunOutput
11
+ from agno.run.team import TeamRunEvent, TeamRunOutput, team_run_output_event_from_dict
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from agno.workflow.types import StepOutput, WorkflowMetrics
@@ -388,6 +388,11 @@ class CustomEvent(BaseWorkflowRunOutputEvent):
388
388
 
389
389
  event: str = WorkflowRunEvent.custom_event.value
390
390
 
391
+ def __init__(self, **kwargs):
392
+ # Store arbitrary attributes directly on the instance
393
+ for key, value in kwargs.items():
394
+ setattr(self, key, value)
395
+
391
396
 
392
397
  # Union type for all workflow run response events
393
398
  WorkflowRunOutputEvent = Union[
@@ -442,10 +447,15 @@ WORKFLOW_RUN_EVENT_TYPE_REGISTRY = {
442
447
 
443
448
  def workflow_run_output_event_from_dict(data: dict) -> BaseWorkflowRunOutputEvent:
444
449
  event_type = data.get("event", "")
445
- cls = WORKFLOW_RUN_EVENT_TYPE_REGISTRY.get(event_type)
446
- if not cls:
450
+ if event_type in {e.value for e in RunEvent}:
451
+ return run_output_event_from_dict(data) # type: ignore
452
+ elif event_type in {e.value for e in TeamRunEvent}:
453
+ return team_run_output_event_from_dict(data) # type: ignore
454
+ else:
455
+ event_class = WORKFLOW_RUN_EVENT_TYPE_REGISTRY.get(event_type)
456
+ if not event_class:
447
457
  raise ValueError(f"Unknown workflow event type: {event_type}")
448
- return cls.from_dict(data) # type: ignore
458
+ return event_class.from_dict(data) # type: ignore
449
459
 
450
460
 
451
461
  @dataclass
agno/session/summary.py CHANGED
@@ -102,7 +102,23 @@ class SessionSummaryManager:
102
102
  system_prompt += "<conversation>"
103
103
  for message in conversation:
104
104
  if message.role == "user":
105
- conversation_messages.append(f"User: {message.content}")
105
+ # Handle empty user messages with media - note what media was provided
106
+ if not message.content or (isinstance(message.content, str) and message.content.strip() == ""):
107
+ media_types = []
108
+ if hasattr(message, "images") and message.images:
109
+ media_types.append(f"{len(message.images)} image(s)")
110
+ if hasattr(message, "videos") and message.videos:
111
+ media_types.append(f"{len(message.videos)} video(s)")
112
+ if hasattr(message, "audio") and message.audio:
113
+ media_types.append(f"{len(message.audio)} audio file(s)")
114
+ if hasattr(message, "files") and message.files:
115
+ media_types.append(f"{len(message.files)} file(s)")
116
+
117
+ if media_types:
118
+ conversation_messages.append(f"User: [Provided {', '.join(media_types)}]")
119
+ # Skip empty messages with no media
120
+ else:
121
+ conversation_messages.append(f"User: {message.content}")
106
122
  elif message.role in ["assistant", "model"]:
107
123
  conversation_messages.append(f"Assistant: {message.content}\n")
108
124
  system_prompt += "\n".join(conversation_messages)
@@ -118,23 +134,27 @@ class SessionSummaryManager:
118
134
  def _prepare_summary_messages(
119
135
  self,
120
136
  session: Optional["Session"] = None,
121
- ) -> List[Message]:
122
- """Prepare messages for session summary generation"""
137
+ ) -> Optional[List[Message]]:
138
+ """Prepare messages for session summary generation. Returns None if no meaningful messages to summarize."""
139
+ if not session:
140
+ return None
141
+
123
142
  self.model = cast(Model, self.model)
124
143
  response_format = self.get_response_format(self.model)
125
144
 
126
- return (
127
- [
128
- self.get_system_message(
129
- conversation=session.get_messages_for_session(), # type: ignore
130
- response_format=response_format,
131
- ),
132
- Message(role="user", content="Provide the summary of the conversation."),
133
- ]
134
- if session
135
- else []
145
+ system_message = self.get_system_message(
146
+ conversation=session.get_messages_for_session(), # type: ignore
147
+ response_format=response_format,
136
148
  )
137
149
 
150
+ if system_message is None:
151
+ return None
152
+
153
+ return [
154
+ system_message,
155
+ Message(role="user", content="Provide the summary of the conversation."),
156
+ ]
157
+
138
158
  def _process_summary_response(self, summary_response, session_summary_model: "Model") -> Optional[SessionSummary]: # type: ignore
139
159
  """Process the model response into a SessionSummary"""
140
160
  from datetime import datetime
@@ -191,6 +211,12 @@ class SessionSummaryManager:
191
211
  return None
192
212
 
193
213
  messages = self._prepare_summary_messages(session)
214
+
215
+ # Skip summary generation if there are no meaningful messages
216
+ if messages is None:
217
+ log_debug("No meaningful messages to summarize, skipping session summary")
218
+ return None
219
+
194
220
  response_format = self.get_response_format(self.model)
195
221
 
196
222
  summary_response = self.model.response(messages=messages, response_format=response_format)
@@ -212,6 +238,12 @@ class SessionSummaryManager:
212
238
  return None
213
239
 
214
240
  messages = self._prepare_summary_messages(session)
241
+
242
+ # Skip summary generation if there are no meaningful messages
243
+ if messages is None:
244
+ log_debug("No meaningful messages to summarize, skipping session summary")
245
+ return None
246
+
215
247
  response_format = self.get_response_format(self.model)
216
248
 
217
249
  summary_response = await self.model.aresponse(messages=messages, response_format=response_format)
agno/session/team.py CHANGED
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import asdict, dataclass
4
- from typing import Any, Dict, List, Mapping, Optional, Union
4
+ from typing import Any, Dict, List, Mapping, Optional, Tuple, Union
5
+
6
+ from pydantic import BaseModel
5
7
 
6
8
  from agno.models.message import Message
7
9
  from agno.run.agent import RunOutput, RunStatus
@@ -243,6 +245,78 @@ class TeamSession:
243
245
  final_messages.append(assistant_message_from_run)
244
246
  return final_messages
245
247
 
248
+ def get_team_history(self, num_runs: Optional[int] = None) -> List[Tuple[str, str]]:
249
+ """Get team history as structured data (input, response pairs) -> This is the history of the team leader, not the members.
250
+
251
+ Args:
252
+ num_runs: Number of recent runs to include. If None, returns all available history.
253
+ """
254
+ if not self.runs:
255
+ return []
256
+
257
+ from agno.run.base import RunStatus
258
+
259
+ # Get completed runs only (exclude current/pending run)
260
+ completed_runs = [run for run in self.runs if run.status == RunStatus.completed and run.parent_run_id is None]
261
+
262
+ if num_runs is not None and len(completed_runs) > num_runs:
263
+ recent_runs = completed_runs[-num_runs:]
264
+ else:
265
+ recent_runs = completed_runs
266
+
267
+ if not recent_runs:
268
+ return []
269
+
270
+ # Return structured data as list of (input, response) tuples
271
+ history_data = []
272
+ for run in recent_runs:
273
+ # Get input
274
+ input_str = ""
275
+ if run.input:
276
+ input_str = run.input.input_content_string()
277
+
278
+ # Get response
279
+ response_str = ""
280
+ if run.content:
281
+ response_str = (
282
+ run.content.model_dump_json(indent=2, exclude_none=True)
283
+ if isinstance(run.content, BaseModel)
284
+ else str(run.content)
285
+ )
286
+
287
+ history_data.append((input_str, response_str))
288
+
289
+ return history_data
290
+
291
+ def get_team_history_context(self, num_runs: Optional[int] = None) -> Optional[str]:
292
+ """Get formatted team history context for steps
293
+
294
+ Args:
295
+ num_runs: Number of recent runs to include. If None, returns all available history.
296
+ """
297
+ history_data = self.get_team_history(num_runs)
298
+
299
+ if not history_data:
300
+ return None
301
+
302
+ # Format as team history context using the structured data
303
+ context_parts = ["<team_history_context>"]
304
+
305
+ for i, (input_str, response_str) in enumerate(history_data, 1):
306
+ context_parts.append(f"[run-{i}]")
307
+
308
+ if input_str:
309
+ context_parts.append(f"input: {input_str}")
310
+ if response_str:
311
+ context_parts.append(f"response: {response_str}")
312
+
313
+ context_parts.append("") # Empty line between runs
314
+
315
+ context_parts.append("</team_history_context>")
316
+ context_parts.append("") # Empty line before current input
317
+
318
+ return "\n".join(context_parts)
319
+
246
320
  def get_session_summary(self) -> Optional[SessionSummary]:
247
321
  """Get the session summary for the session"""
248
322
 
@@ -252,17 +326,28 @@ class TeamSession:
252
326
  return self.summary # type: ignore
253
327
 
254
328
  # Chat History functions
255
- def get_chat_history(self) -> List[Message]:
256
- """Get the chat history for the session"""
329
+ def get_chat_history(
330
+ self, skip_history_messages: bool = True, skip_roles: Optional[List[str]] = None
331
+ ) -> List[Message]:
332
+ """
333
+ Get the chat history for the session.
334
+ This is all messages across all runs for the team leader.
335
+ """
257
336
 
258
337
  messages = []
259
338
  if self.runs is None:
260
339
  return []
261
340
 
262
341
  for run in self.runs or []:
263
- if run.messages is None:
342
+ if run.parent_run_id is not None:
264
343
  continue
265
344
 
266
- messages.extend([msg for msg in run.messages or [] if not msg.from_history])
345
+ if run.messages is not None:
346
+ for msg in run.messages or []:
347
+ if skip_history_messages and msg.from_history:
348
+ continue
349
+ if skip_roles and msg.role in skip_roles:
350
+ continue
351
+ messages.append(msg)
267
352
 
268
353
  return messages