agno 2.3.5__py3-none-any.whl → 2.3.7__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.
@@ -32,6 +32,7 @@ from agno.utils.log import log_debug, log_error, log_info, log_warning
32
32
  try:
33
33
  from sqlalchemy import Index, String, Table, UniqueConstraint, func, update
34
34
  from sqlalchemy.dialects import postgresql
35
+ from sqlalchemy.exc import ProgrammingError
35
36
  from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker, create_async_engine
36
37
  from sqlalchemy.schema import Column, MetaData
37
38
  from sqlalchemy.sql.expression import select, text
@@ -254,47 +255,63 @@ class AsyncPostgresDb(AsyncBaseDb):
254
255
  if table_type == "sessions":
255
256
  if not hasattr(self, "session_table"):
256
257
  self.session_table = await self._get_or_create_table(
257
- table_name=self.session_table_name, table_type="sessions"
258
+ table_name=self.session_table_name,
259
+ table_type="sessions",
260
+ create_table_if_not_found=create_table_if_not_found,
258
261
  )
259
262
  return self.session_table
260
263
 
261
264
  if table_type == "memories":
262
265
  if not hasattr(self, "memory_table"):
263
266
  self.memory_table = await self._get_or_create_table(
264
- table_name=self.memory_table_name, table_type="memories"
267
+ table_name=self.memory_table_name,
268
+ table_type="memories",
269
+ create_table_if_not_found=create_table_if_not_found,
265
270
  )
266
271
  return self.memory_table
267
272
 
268
273
  if table_type == "metrics":
269
274
  if not hasattr(self, "metrics_table"):
270
275
  self.metrics_table = await self._get_or_create_table(
271
- table_name=self.metrics_table_name, table_type="metrics"
276
+ table_name=self.metrics_table_name,
277
+ table_type="metrics",
278
+ create_table_if_not_found=create_table_if_not_found,
272
279
  )
273
280
  return self.metrics_table
274
281
 
275
282
  if table_type == "evals":
276
283
  if not hasattr(self, "eval_table"):
277
- self.eval_table = await self._get_or_create_table(table_name=self.eval_table_name, table_type="evals")
284
+ self.eval_table = await self._get_or_create_table(
285
+ table_name=self.eval_table_name,
286
+ table_type="evals",
287
+ create_table_if_not_found=create_table_if_not_found,
288
+ )
278
289
  return self.eval_table
279
290
 
280
291
  if table_type == "knowledge":
281
292
  if not hasattr(self, "knowledge_table"):
282
293
  self.knowledge_table = await self._get_or_create_table(
283
- table_name=self.knowledge_table_name, table_type="knowledge"
294
+ table_name=self.knowledge_table_name,
295
+ table_type="knowledge",
296
+ create_table_if_not_found=create_table_if_not_found,
284
297
  )
285
298
  return self.knowledge_table
286
299
 
287
300
  if table_type == "culture":
288
301
  if not hasattr(self, "culture_table"):
289
302
  self.culture_table = await self._get_or_create_table(
290
- table_name=self.culture_table_name, table_type="culture"
303
+ table_name=self.culture_table_name,
304
+ table_type="culture",
305
+ create_table_if_not_found=create_table_if_not_found,
291
306
  )
292
307
  return self.culture_table
293
308
 
294
309
  if table_type == "versions":
295
310
  if not hasattr(self, "versions_table"):
296
311
  self.versions_table = await self._get_or_create_table(
297
- table_name=self.versions_table_name, table_type="versions"
312
+ table_name=self.versions_table_name,
313
+ table_type="versions",
314
+ create_table_if_not_found=create_table_if_not_found,
298
315
  )
299
316
  return self.versions_table
300
317
 
@@ -897,10 +914,19 @@ class AsyncPostgresDb(AsyncBaseDb):
897
914
  table = await self._get_table(table_type="memories")
898
915
 
899
916
  async with self.async_session_factory() as sess, sess.begin():
900
- stmt = select(func.json_array_elements_text(table.c.topics))
901
- if user_id is not None:
902
- stmt = stmt.where(table.c.user_id == user_id)
903
- result = await sess.execute(stmt)
917
+ try:
918
+ stmt = select(func.jsonb_array_elements_text(table.c.topics))
919
+ if user_id is not None:
920
+ stmt = stmt.where(table.c.user_id == user_id)
921
+ result = await sess.execute(stmt)
922
+ except ProgrammingError:
923
+ # Retrying with json_array_elements_text. This works in older versions,
924
+ # where the topics column was of type JSON instead of JSONB
925
+ stmt = select(func.json_array_elements_text(table.c.topics))
926
+ if user_id is not None:
927
+ stmt = stmt.where(table.c.user_id == user_id)
928
+ result = await sess.execute(stmt)
929
+
904
930
  records = result.fetchall()
905
931
 
906
932
  return list(set([record[0] for record in records]))
@@ -33,6 +33,7 @@ try:
33
33
  from sqlalchemy import ForeignKey, Index, String, UniqueConstraint, func, select, update
34
34
  from sqlalchemy.dialects import postgresql
35
35
  from sqlalchemy.engine import Engine, create_engine
36
+ from sqlalchemy.exc import ProgrammingError
36
37
  from sqlalchemy.orm import scoped_session, sessionmaker
37
38
  from sqlalchemy.schema import Column, MetaData, Table
38
39
  from sqlalchemy.sql.expression import text
@@ -1082,9 +1083,14 @@ class PostgresDb(BaseDb):
1082
1083
  return []
1083
1084
 
1084
1085
  with self.Session() as sess, sess.begin():
1085
- stmt = select(func.json_array_elements_text(table.c.topics))
1086
-
1087
- result = sess.execute(stmt).fetchall()
1086
+ try:
1087
+ stmt = select(func.jsonb_array_elements_text(table.c.topics))
1088
+ result = sess.execute(stmt).fetchall()
1089
+ except ProgrammingError:
1090
+ # Retrying with json_array_elements_text. This works in older versions,
1091
+ # where the topics column was of type JSON instead of JSONB
1092
+ stmt = select(func.json_array_elements_text(table.c.topics))
1093
+ result = sess.execute(stmt).fetchall()
1088
1094
 
1089
1095
  return list(set([record[0] for record in result]))
1090
1096
 
@@ -1114,7 +1114,7 @@ class AsyncSqliteDb(AsyncBaseDb):
1114
1114
 
1115
1115
  async with self.async_session_factory() as sess, sess.begin():
1116
1116
  # Select topics from all results
1117
- stmt = select(func.json_array_elements_text(table.c.topics)).select_from(table)
1117
+ stmt = select(table.c.topics)
1118
1118
  result = (await sess.execute(stmt)).fetchall()
1119
1119
 
1120
1120
  return list(set([record[0] for record in result]))
agno/db/sqlite/sqlite.py CHANGED
@@ -245,9 +245,9 @@ class SqliteDb(BaseDb):
245
245
  return table
246
246
 
247
247
  except Exception as e:
248
- from traceback import format_exc
248
+ from traceback import print_exc
249
249
 
250
- print(format_exc())
250
+ print_exc()
251
251
  log_error(f"Could not create table '{table_name}': {e}")
252
252
  raise e
253
253
 
@@ -1109,9 +1109,8 @@ class SqliteDb(BaseDb):
1109
1109
 
1110
1110
  with self.Session() as sess, sess.begin():
1111
1111
  # Select topics from all results
1112
- stmt = select(func.json_array_elements_text(table.c.topics)).select_from(table)
1112
+ stmt = select(table.c.topics)
1113
1113
  result = sess.execute(stmt).fetchall()
1114
-
1115
1114
  return list(set([record[0] for record in result]))
1116
1115
 
1117
1116
  except Exception as e:
agno/db/utils.py CHANGED
@@ -20,6 +20,8 @@ class CustomJSONEncoder(json.JSONEncoder):
20
20
  return obj.to_dict()
21
21
  elif isinstance(obj, Metrics):
22
22
  return obj.to_dict()
23
+ elif isinstance(obj, type):
24
+ return str(obj)
23
25
 
24
26
  return super().default(obj)
25
27
 
agno/eval/accuracy.py CHANGED
@@ -359,10 +359,12 @@ Remember: You must only compare the agent_output to the expected_output. The exp
359
359
  status = Status(f"Running evaluation {i + 1}...", spinner="dots", speed=1.0, refresh_per_second=10)
360
360
  live_log.update(status)
361
361
 
362
+ agent_session_id = f"eval_{self.eval_id}_{i + 1}"
363
+
362
364
  if self.agent is not None:
363
- output = self.agent.run(input=eval_input).content
365
+ output = self.agent.run(input=eval_input, session_id=agent_session_id).content
364
366
  elif self.team is not None:
365
- output = self.team.run(input=eval_input).content
367
+ output = self.team.run(input=eval_input, session_id=agent_session_id).content
366
368
 
367
369
  if not output:
368
370
  logger.error(f"Failed to generate a valid answer on iteration {i + 1}: {output}")
@@ -500,11 +502,13 @@ Remember: You must only compare the agent_output to the expected_output. The exp
500
502
  status = Status(f"Running evaluation {i + 1}...", spinner="dots", speed=1.0, refresh_per_second=10)
501
503
  live_log.update(status)
502
504
 
505
+ agent_session_id = f"eval_{self.eval_id}_{i + 1}"
506
+
503
507
  if self.agent is not None:
504
- response = await self.agent.arun(input=eval_input)
508
+ response = await self.agent.arun(input=eval_input, session_id=agent_session_id)
505
509
  output = response.content
506
510
  elif self.team is not None:
507
- response = await self.team.arun(input=eval_input) # type: ignore
511
+ response = await self.team.arun(input=eval_input, session_id=agent_session_id) # type: ignore
508
512
  output = response.content
509
513
 
510
514
  if not output:
@@ -14,7 +14,7 @@ try:
14
14
  import discord
15
15
 
16
16
  except (ImportError, ModuleNotFoundError):
17
- print("`discord.py` not installed. Please install using `pip install discord.py`")
17
+ raise ImportError("`discord.py` not installed. Please install using `pip install discord.py`")
18
18
 
19
19
 
20
20
  class RequiresConfirmationView(discord.ui.View):
agno/models/base.py CHANGED
@@ -30,6 +30,7 @@ from agno.models.message import Citations, Message
30
30
  from agno.models.metrics import Metrics
31
31
  from agno.models.response import ModelResponse, ModelResponseEvent, ToolExecution
32
32
  from agno.run.agent import CustomEvent, RunContentEvent, RunOutput, RunOutputEvent
33
+ from agno.run.requirement import RunRequirement
33
34
  from agno.run.team import RunContentEvent as TeamRunContentEvent
34
35
  from agno.run.team import TeamRunOutput, TeamRunOutputEvent
35
36
  from agno.run.workflow import WorkflowRunOutputEvent
@@ -423,10 +424,23 @@ class Model(ABC):
423
424
  ]
424
425
  and function_call_response.tool_executions is not None
425
426
  ):
427
+ # Record the tool execution in the model response
426
428
  if model_response.tool_executions is None:
427
429
  model_response.tool_executions = []
428
430
  model_response.tool_executions.extend(function_call_response.tool_executions)
429
431
 
432
+ # If the tool is currently paused (HITL flow), add the requirement to the run response
433
+ if (
434
+ function_call_response.event == ModelResponseEvent.tool_call_paused.value
435
+ and run_response is not None
436
+ ):
437
+ current_tool_execution = function_call_response.tool_executions[-1]
438
+ if run_response.requirements is None:
439
+ run_response.requirements = []
440
+ run_response.requirements.append(
441
+ RunRequirement(tool_execution=current_tool_execution)
442
+ )
443
+
430
444
  elif function_call_response.event not in [
431
445
  ModelResponseEvent.tool_call_started.value,
432
446
  ModelResponseEvent.tool_call_completed.value,
@@ -615,6 +629,19 @@ class Model(ABC):
615
629
  if model_response.tool_executions is None:
616
630
  model_response.tool_executions = []
617
631
  model_response.tool_executions.extend(function_call_response.tool_executions)
632
+
633
+ # If the tool is currently paused (HITL flow), add the requirement to the run response
634
+ if (
635
+ function_call_response.event == ModelResponseEvent.tool_call_paused.value
636
+ and run_response is not None
637
+ ):
638
+ current_tool_execution = function_call_response.tool_executions[-1]
639
+ if run_response.requirements is None:
640
+ run_response.requirements = []
641
+ run_response.requirements.append(
642
+ RunRequirement(tool_execution=current_tool_execution)
643
+ )
644
+
618
645
  elif function_call_response.event not in [
619
646
  ModelResponseEvent.tool_call_started.value,
620
647
  ModelResponseEvent.tool_call_completed.value,
@@ -1706,7 +1733,7 @@ class Model(ABC):
1706
1733
 
1707
1734
  paused_tool_executions = []
1708
1735
 
1709
- # The function cannot be executed without user confirmation
1736
+ # The function requires user confirmation (HITL)
1710
1737
  if fc.function.requires_confirmation:
1711
1738
  paused_tool_executions.append(
1712
1739
  ToolExecution(
@@ -1716,7 +1743,8 @@ class Model(ABC):
1716
1743
  requires_confirmation=True,
1717
1744
  )
1718
1745
  )
1719
- # If the function requires user input, we yield a message to the user
1746
+
1747
+ # The function requires user input (HITL)
1720
1748
  if fc.function.requires_user_input:
1721
1749
  user_input_schema = fc.function.user_input_schema
1722
1750
  if fc.arguments and user_input_schema:
@@ -1734,7 +1762,8 @@ class Model(ABC):
1734
1762
  user_input_schema=user_input_schema,
1735
1763
  )
1736
1764
  )
1737
- # If the function is from the user control flow tools, we handle it here
1765
+
1766
+ # If the function is from the user control flow (HITL) tools, we handle it here
1738
1767
  if fc.function.name == "get_user_input" and fc.arguments and fc.arguments.get("user_input_fields"):
1739
1768
  user_input_schema = []
1740
1769
  for input_field in fc.arguments.get("user_input_fields", []):
@@ -1760,7 +1789,8 @@ class Model(ABC):
1760
1789
  user_input_schema=user_input_schema,
1761
1790
  )
1762
1791
  )
1763
- # If the function requires external execution, we yield a message to the user
1792
+
1793
+ # The function requires external execution (HITL)
1764
1794
  if fc.function.external_execution:
1765
1795
  paused_tool_executions.append(
1766
1796
  ToolExecution(
@@ -512,14 +512,16 @@ class Cerebras(Model):
512
512
 
513
513
  # Extend the list if needed
514
514
  while len(tool_calls) <= index:
515
- tool_calls.append({
516
- "id": None,
517
- "type": None,
518
- "function": {
519
- "name": "",
520
- "arguments": "",
521
- },
522
- })
515
+ tool_calls.append(
516
+ {
517
+ "id": None,
518
+ "type": None,
519
+ "function": {
520
+ "name": "",
521
+ "arguments": "",
522
+ },
523
+ }
524
+ )
523
525
 
524
526
  tool_call_entry = tool_calls[index]
525
527
 
@@ -540,10 +542,7 @@ class Cerebras(Model):
540
542
  tool_call_entry["function"]["arguments"] += func_delta["arguments"]
541
543
 
542
544
  # Filter out any incomplete tool calls (missing id or function name)
543
- complete_tool_calls = [
544
- tc for tc in tool_calls
545
- if tc.get("id") and tc.get("function", {}).get("name")
546
- ]
545
+ complete_tool_calls = [tc for tc in tool_calls if tc.get("id") and tc.get("function", {}).get("name")]
547
546
 
548
547
  return complete_tool_calls
549
548
 
agno/models/response.py CHANGED
@@ -37,7 +37,7 @@ class ToolExecution:
37
37
 
38
38
  created_at: int = field(default_factory=lambda: int(time()))
39
39
 
40
- # User control flow requirements
40
+ # User control flow (HITL) fields
41
41
  requires_confirmation: Optional[bool] = None
42
42
  confirmed: Optional[bool] = None
43
43
  confirmation_note: Optional[str] = None
@@ -3,6 +3,8 @@ import hmac
3
3
  import os
4
4
  from typing import Optional
5
5
 
6
+ from agno.utils.log import log_warning
7
+
6
8
 
7
9
  def is_development_mode() -> bool:
8
10
  """Check if the application is running in development mode."""
@@ -36,7 +38,7 @@ def validate_webhook_signature(payload: bytes, signature_header: Optional[str])
36
38
  """
37
39
  # In development mode, we can bypass signature validation
38
40
  if is_development_mode():
39
- print("WARNING: Bypassing signature validation in development mode")
41
+ log_warning("Bypassing signature validation in development mode")
40
42
  return True
41
43
 
42
44
  if not signature_header or not signature_header.startswith("sha256="):
@@ -36,7 +36,10 @@ async def run_accuracy_eval(
36
36
  model=default_model,
37
37
  )
38
38
 
39
- result = accuracy_eval.run(print_results=False, print_summary=False)
39
+ if isinstance(db, AsyncBaseDb):
40
+ result = await accuracy_eval.arun(print_results=False, print_summary=False)
41
+ else:
42
+ result = accuracy_eval.run(print_results=False, print_summary=False)
40
43
  if not result:
41
44
  raise HTTPException(status_code=500, detail="Failed to run accuracy evaluation")
42
45
 
@@ -86,7 +89,11 @@ async def run_performance_eval(
86
89
  model_id=model_id,
87
90
  model_provider=model_provider,
88
91
  )
89
- result = performance_eval.run(print_results=False, print_summary=False)
92
+
93
+ if isinstance(db, AsyncBaseDb):
94
+ result = await performance_eval.arun(print_results=False, print_summary=False)
95
+ else:
96
+ result = performance_eval.run(print_results=False, print_summary=False)
90
97
  if not result:
91
98
  raise HTTPException(status_code=500, detail="Failed to run performance evaluation")
92
99
 
@@ -141,7 +148,10 @@ async def run_reliability_eval(
141
148
  model_id = team.model.id if team and team.model else None
142
149
  model_provider = team.model.provider if team and team.model else None
143
150
 
144
- result = reliability_eval.run(print_results=False)
151
+ if isinstance(db, AsyncBaseDb):
152
+ result = await reliability_eval.arun(print_results=False)
153
+ else:
154
+ result = reliability_eval.run(print_results=False)
145
155
  if not result:
146
156
  raise HTTPException(status_code=500, detail="Failed to run reliability evaluation")
147
157
 
agno/os/schema.py CHANGED
@@ -742,10 +742,11 @@ class SessionSchema(BaseModel):
742
742
  @classmethod
743
743
  def from_dict(cls, session: Dict[str, Any]) -> "SessionSchema":
744
744
  session_name = get_session_name(session)
745
+ session_data = session.get("session_data", {}) or {}
745
746
  return cls(
746
747
  session_id=session.get("session_id", ""),
747
748
  session_name=session_name,
748
- session_state=session.get("session_data", {}).get("session_state", None),
749
+ session_state=session_data.get("session_state", None),
749
750
  created_at=datetime.fromtimestamp(session.get("created_at", 0), tz=timezone.utc)
750
751
  if session.get("created_at")
751
752
  else None,
agno/run/agent.py CHANGED
@@ -11,6 +11,7 @@ from agno.models.metrics import Metrics
11
11
  from agno.models.response import ToolExecution
12
12
  from agno.reasoning.step import ReasoningStep
13
13
  from agno.run.base import BaseRunOutputEvent, MessageReferences, RunStatus
14
+ from agno.run.requirement import RunRequirement
14
15
  from agno.utils.log import logger
15
16
  from agno.utils.media import (
16
17
  reconstruct_audio_list,
@@ -273,11 +274,18 @@ class RunCompletedEvent(BaseAgentRunEvent):
273
274
  class RunPausedEvent(BaseAgentRunEvent):
274
275
  event: str = RunEvent.run_paused.value
275
276
  tools: Optional[List[ToolExecution]] = None
277
+ requirements: Optional[List[RunRequirement]] = None
276
278
 
277
279
  @property
278
280
  def is_paused(self):
279
281
  return True
280
282
 
283
+ @property
284
+ def active_requirements(self) -> List[RunRequirement]:
285
+ if not self.requirements:
286
+ return []
287
+ return [requirement for requirement in self.requirements if not requirement.is_resolved()]
288
+
281
289
 
282
290
  @dataclass
283
291
  class RunContinuedEvent(BaseAgentRunEvent):
@@ -539,11 +547,20 @@ class RunOutput:
539
547
 
540
548
  status: RunStatus = RunStatus.running
541
549
 
550
+ # User control flow (HITL) requirements to continue a run when paused, in order of arrival
551
+ requirements: Optional[list[RunRequirement]] = None
552
+
542
553
  # === FOREIGN KEY RELATIONSHIPS ===
543
554
  # These fields establish relationships to parent workflow/step structures
544
555
  # and should be treated as foreign keys for data integrity
545
556
  workflow_step_id: Optional[str] = None # FK: Points to StepOutput.step_id
546
557
 
558
+ @property
559
+ def active_requirements(self) -> list[RunRequirement]:
560
+ if not self.requirements:
561
+ return []
562
+ return [requirement for requirement in self.requirements if not requirement.is_resolved()]
563
+
547
564
  @property
548
565
  def is_paused(self):
549
566
  return self.status == RunStatus.paused
@@ -0,0 +1,98 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime, timezone
3
+ from typing import TYPE_CHECKING, List, Optional
4
+ from uuid import uuid4
5
+
6
+ from agno.models.response import ToolExecution, UserInputField
7
+
8
+ if TYPE_CHECKING:
9
+ pass
10
+
11
+
12
+ @dataclass
13
+ class RunRequirement:
14
+ """Requirement to complete a paused run (used in HITL flows)"""
15
+
16
+ tool_execution: Optional[ToolExecution] = None
17
+ created_at: datetime = datetime.now(timezone.utc)
18
+
19
+ # User confirmation
20
+ confirmation: Optional[bool] = None
21
+ confirmation_note: Optional[str] = None
22
+
23
+ # User input
24
+ user_input_schema: Optional[List[UserInputField]] = None
25
+
26
+ # External execution
27
+ external_execution_result: Optional[str] = None
28
+
29
+ def __init__(self, tool_execution: ToolExecution):
30
+ self.id = str(uuid4())
31
+ self.tool_execution = tool_execution
32
+ self.user_input_schema = tool_execution.user_input_schema
33
+
34
+ @property
35
+ def needs_confirmation(self) -> bool:
36
+ if self.confirmation is not None:
37
+ return False
38
+ if not self.tool_execution:
39
+ return False
40
+ if self.tool_execution.confirmed is True:
41
+ return True
42
+
43
+ return self.tool_execution.requires_confirmation or False
44
+
45
+ @property
46
+ def needs_user_input(self) -> bool:
47
+ if not self.tool_execution:
48
+ return False
49
+ if self.tool_execution.answered is True:
50
+ return False
51
+ if self.user_input_schema and not all(field.value is not None for field in self.user_input_schema):
52
+ return True
53
+
54
+ return self.tool_execution.requires_user_input or False
55
+
56
+ @property
57
+ def needs_external_execution(self) -> bool:
58
+ if not self.tool_execution:
59
+ return False
60
+ if self.external_execution_result is not None:
61
+ return True
62
+
63
+ return self.tool_execution.external_execution_required or False
64
+
65
+ def confirm(self):
66
+ if not self.needs_confirmation:
67
+ raise ValueError("This requirement does not require confirmation")
68
+ self.confirmation = True
69
+ if self.tool_execution:
70
+ self.tool_execution.confirmed = True
71
+
72
+ def reject(self):
73
+ if not self.needs_confirmation:
74
+ raise ValueError("This requirement does not require confirmation")
75
+ self.confirmation = False
76
+ if self.tool_execution:
77
+ self.tool_execution.confirmed = False
78
+
79
+ def set_external_execution_result(self, result: str):
80
+ if not self.needs_external_execution:
81
+ raise ValueError("This requirement does not require external execution")
82
+ self.external_execution_result = result
83
+ if self.tool_execution:
84
+ self.tool_execution.result = result
85
+
86
+ def update_tool(self):
87
+ if not self.tool_execution:
88
+ return
89
+ if self.confirmation is True:
90
+ self.tool_execution.confirmed = True
91
+ elif self.confirmation is False:
92
+ self.tool_execution.confirmed = False
93
+ else:
94
+ raise ValueError("This requirement does not require confirmation or user input")
95
+
96
+ def is_resolved(self) -> bool:
97
+ """Return True if the requirement has been resolved"""
98
+ return not self.needs_confirmation and not self.needs_user_input and not self.needs_external_execution
agno/run/team.py CHANGED
@@ -12,6 +12,7 @@ from agno.models.response import ToolExecution
12
12
  from agno.reasoning.step import ReasoningStep
13
13
  from agno.run.agent import RunEvent, RunOutput, RunOutputEvent, run_output_event_from_dict
14
14
  from agno.run.base import BaseRunOutputEvent, MessageReferences, RunStatus
15
+ from agno.run.requirement import RunRequirement
15
16
  from agno.utils.log import log_error
16
17
  from agno.utils.media import (
17
18
  reconstruct_audio_list,
@@ -515,11 +516,20 @@ class TeamRunOutput:
515
516
 
516
517
  status: RunStatus = RunStatus.running
517
518
 
519
+ # User control flow (HITL) requirements to continue a run when paused, in order of arrival
520
+ requirements: Optional[list[RunRequirement]] = None
521
+
518
522
  # === FOREIGN KEY RELATIONSHIPS ===
519
523
  # These fields establish relationships to parent workflow/step structures
520
524
  # and should be treated as foreign keys for data integrity
521
525
  workflow_step_id: Optional[str] = None # FK: Points to StepOutput.step_id
522
526
 
527
+ @property
528
+ def active_requirements(self) -> list[RunRequirement]:
529
+ if not self.requirements:
530
+ return []
531
+ return [requirement for requirement in self.requirements if not requirement.is_resolved()]
532
+
523
533
  @property
524
534
  def is_paused(self):
525
535
  return self.status == RunStatus.paused
agno/session/team.py CHANGED
@@ -46,7 +46,6 @@ class TeamSession:
46
46
  session_dict = asdict(self)
47
47
 
48
48
  session_dict["runs"] = [run.to_dict() for run in self.runs] if self.runs else None
49
- print(session_dict["runs"])
50
49
  session_dict["summary"] = self.summary.to_dict() if self.summary else None
51
50
 
52
51
  return session_dict
agno/table.py CHANGED
@@ -7,4 +7,4 @@ conn = sqlite3.connect(db_path)
7
7
  cur = conn.cursor()
8
8
  cur.execute(f"DROP TABLE IF EXISTS {table_name}")
9
9
  conn.commit()
10
- conn.close()
10
+ conn.close()