agno 2.3.25__py3-none-any.whl → 2.4.0__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 (128) hide show
  1. agno/agent/__init__.py +4 -0
  2. agno/agent/agent.py +1428 -558
  3. agno/agent/remote.py +13 -0
  4. agno/db/base.py +339 -0
  5. agno/db/postgres/async_postgres.py +116 -12
  6. agno/db/postgres/postgres.py +1229 -25
  7. agno/db/postgres/schemas.py +48 -1
  8. agno/db/sqlite/async_sqlite.py +119 -4
  9. agno/db/sqlite/schemas.py +51 -0
  10. agno/db/sqlite/sqlite.py +1173 -13
  11. agno/db/utils.py +37 -1
  12. agno/knowledge/__init__.py +4 -0
  13. agno/knowledge/chunking/code.py +1 -1
  14. agno/knowledge/chunking/semantic.py +1 -1
  15. agno/knowledge/chunking/strategy.py +4 -0
  16. agno/knowledge/filesystem.py +412 -0
  17. agno/knowledge/knowledge.py +2767 -2254
  18. agno/knowledge/protocol.py +134 -0
  19. agno/knowledge/reader/arxiv_reader.py +2 -2
  20. agno/knowledge/reader/base.py +9 -7
  21. agno/knowledge/reader/csv_reader.py +5 -5
  22. agno/knowledge/reader/docx_reader.py +2 -2
  23. agno/knowledge/reader/field_labeled_csv_reader.py +2 -2
  24. agno/knowledge/reader/firecrawl_reader.py +2 -2
  25. agno/knowledge/reader/json_reader.py +2 -2
  26. agno/knowledge/reader/markdown_reader.py +2 -2
  27. agno/knowledge/reader/pdf_reader.py +5 -4
  28. agno/knowledge/reader/pptx_reader.py +2 -2
  29. agno/knowledge/reader/reader_factory.py +110 -0
  30. agno/knowledge/reader/s3_reader.py +2 -2
  31. agno/knowledge/reader/tavily_reader.py +2 -2
  32. agno/knowledge/reader/text_reader.py +2 -2
  33. agno/knowledge/reader/web_search_reader.py +2 -2
  34. agno/knowledge/reader/website_reader.py +5 -3
  35. agno/knowledge/reader/wikipedia_reader.py +2 -2
  36. agno/knowledge/reader/youtube_reader.py +2 -2
  37. agno/knowledge/utils.py +37 -29
  38. agno/learn/__init__.py +6 -0
  39. agno/learn/machine.py +35 -0
  40. agno/learn/schemas.py +82 -11
  41. agno/learn/stores/__init__.py +3 -0
  42. agno/learn/stores/decision_log.py +1156 -0
  43. agno/learn/stores/learned_knowledge.py +6 -6
  44. agno/models/anthropic/claude.py +24 -0
  45. agno/models/aws/bedrock.py +20 -0
  46. agno/models/base.py +48 -4
  47. agno/models/cohere/chat.py +25 -0
  48. agno/models/google/gemini.py +50 -5
  49. agno/models/litellm/chat.py +38 -0
  50. agno/models/openai/chat.py +7 -0
  51. agno/models/openrouter/openrouter.py +46 -0
  52. agno/models/response.py +16 -0
  53. agno/os/app.py +83 -44
  54. agno/os/middleware/__init__.py +2 -0
  55. agno/os/middleware/trailing_slash.py +27 -0
  56. agno/os/router.py +1 -0
  57. agno/os/routers/agents/router.py +29 -16
  58. agno/os/routers/agents/schema.py +6 -4
  59. agno/os/routers/components/__init__.py +3 -0
  60. agno/os/routers/components/components.py +466 -0
  61. agno/os/routers/evals/schemas.py +4 -3
  62. agno/os/routers/health.py +3 -3
  63. agno/os/routers/knowledge/knowledge.py +3 -3
  64. agno/os/routers/memory/schemas.py +4 -2
  65. agno/os/routers/metrics/metrics.py +9 -11
  66. agno/os/routers/metrics/schemas.py +10 -6
  67. agno/os/routers/registry/__init__.py +3 -0
  68. agno/os/routers/registry/registry.py +337 -0
  69. agno/os/routers/teams/router.py +20 -8
  70. agno/os/routers/teams/schema.py +6 -4
  71. agno/os/routers/traces/traces.py +5 -5
  72. agno/os/routers/workflows/router.py +38 -11
  73. agno/os/routers/workflows/schema.py +1 -1
  74. agno/os/schema.py +92 -26
  75. agno/os/utils.py +133 -16
  76. agno/reasoning/anthropic.py +2 -2
  77. agno/reasoning/azure_ai_foundry.py +2 -2
  78. agno/reasoning/deepseek.py +2 -2
  79. agno/reasoning/default.py +6 -7
  80. agno/reasoning/gemini.py +2 -2
  81. agno/reasoning/helpers.py +6 -7
  82. agno/reasoning/manager.py +4 -10
  83. agno/reasoning/ollama.py +2 -2
  84. agno/reasoning/openai.py +2 -2
  85. agno/reasoning/vertexai.py +2 -2
  86. agno/registry/__init__.py +3 -0
  87. agno/registry/registry.py +68 -0
  88. agno/run/agent.py +57 -0
  89. agno/run/base.py +7 -0
  90. agno/run/team.py +57 -0
  91. agno/skills/agent_skills.py +10 -3
  92. agno/team/__init__.py +3 -1
  93. agno/team/team.py +1276 -326
  94. agno/tools/duckduckgo.py +25 -71
  95. agno/tools/exa.py +0 -21
  96. agno/tools/function.py +35 -83
  97. agno/tools/knowledge.py +9 -4
  98. agno/tools/mem0.py +11 -10
  99. agno/tools/memory.py +47 -46
  100. agno/tools/parallel.py +0 -7
  101. agno/tools/reasoning.py +30 -23
  102. agno/tools/tavily.py +4 -1
  103. agno/tools/websearch.py +93 -0
  104. agno/tools/website.py +1 -1
  105. agno/tools/wikipedia.py +1 -1
  106. agno/tools/workflow.py +48 -47
  107. agno/utils/agent.py +42 -5
  108. agno/utils/events.py +160 -2
  109. agno/utils/print_response/agent.py +0 -31
  110. agno/utils/print_response/team.py +0 -2
  111. agno/utils/print_response/workflow.py +0 -2
  112. agno/utils/team.py +61 -11
  113. agno/vectordb/lancedb/lance_db.py +4 -1
  114. agno/vectordb/mongodb/mongodb.py +1 -1
  115. agno/vectordb/qdrant/qdrant.py +4 -4
  116. agno/workflow/__init__.py +3 -1
  117. agno/workflow/condition.py +0 -21
  118. agno/workflow/loop.py +0 -21
  119. agno/workflow/parallel.py +0 -21
  120. agno/workflow/router.py +0 -21
  121. agno/workflow/step.py +117 -24
  122. agno/workflow/steps.py +0 -21
  123. agno/workflow/workflow.py +625 -63
  124. {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/METADATA +46 -76
  125. {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/RECORD +128 -117
  126. {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/WHEEL +0 -0
  127. {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/licenses/LICENSE +0 -0
  128. {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/top_level.txt +0 -0
agno/workflow/workflow.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import asyncio
2
- import warnings
3
2
  from dataclasses import dataclass
4
3
  from datetime import datetime
5
4
  from os import getenv
@@ -29,11 +28,13 @@ if TYPE_CHECKING:
29
28
  from agno.os.managers import WebSocketHandler
30
29
 
31
30
  from agno.agent.agent import Agent
32
- from agno.db.base import AsyncBaseDb, BaseDb, SessionType
31
+ from agno.db.base import AsyncBaseDb, BaseDb, ComponentType, SessionType
32
+ from agno.db.utils import db_from_dict
33
33
  from agno.exceptions import InputCheckError, OutputCheckError, RunCancelledException
34
34
  from agno.media import Audio, File, Image, Video
35
35
  from agno.models.message import Message
36
36
  from agno.models.metrics import Metrics
37
+ from agno.registry import Registry
37
38
  from agno.run import RunContext, RunStatus
38
39
  from agno.run.agent import RunContentEvent, RunEvent, RunOutput
39
40
  from agno.run.cancel import (
@@ -79,6 +80,7 @@ from agno.utils.print_response.workflow import (
79
80
  print_response,
80
81
  print_response_stream,
81
82
  )
83
+ from agno.utils.string import generate_id_from_name
82
84
  from agno.workflow.agent import WorkflowAgent
83
85
  from agno.workflow.condition import Condition
84
86
  from agno.workflow.loop import Loop
@@ -192,9 +194,6 @@ class Workflow:
192
194
  # Number of historical runs to include in the messages
193
195
  num_history_runs: int = 3
194
196
 
195
- # Deprecated. Use stream_events instead.
196
- stream_intermediate_steps: bool = False
197
-
198
197
  # If True, run hooks as FastAPI background tasks (non-blocking). Set by AgentOS.
199
198
  _run_hooks_in_background: bool = False
200
199
 
@@ -214,7 +213,6 @@ class Workflow:
214
213
  debug_mode: Optional[bool] = False,
215
214
  stream: Optional[bool] = None,
216
215
  stream_events: bool = False,
217
- stream_intermediate_steps: bool = False,
218
216
  stream_executor_events: bool = True,
219
217
  store_events: bool = False,
220
218
  events_to_skip: Optional[List[Union[WorkflowRunEvent, RunEvent, TeamRunEvent]]] = None,
@@ -250,14 +248,7 @@ class Workflow:
250
248
  self.add_workflow_history_to_steps = add_workflow_history_to_steps
251
249
  self.num_history_runs = num_history_runs
252
250
  self._workflow_session: Optional[WorkflowSession] = None
253
-
254
- if stream_intermediate_steps:
255
- warnings.warn(
256
- "The 'stream_intermediate_steps' parameter is deprecated and will be removed in future versions. Use 'stream_events' instead.",
257
- DeprecationWarning,
258
- stacklevel=2,
259
- )
260
- self.stream_events = stream_events or stream_intermediate_steps
251
+ self.stream_events = stream_events
261
252
 
262
253
  # Warn if workflow history is enabled without a database
263
254
  if self.add_workflow_history_to_steps and self.db is None:
@@ -268,10 +259,7 @@ class Workflow:
268
259
 
269
260
  def set_id(self) -> None:
270
261
  if self.id is None:
271
- if self.name is not None:
272
- self.id = self.name.lower().replace(" ", "-")
273
- else:
274
- self.id = str(uuid4())
262
+ self.id = generate_id_from_name(self.name)
275
263
 
276
264
  def _has_async_db(self) -> bool:
277
265
  return self.db is not None and isinstance(self.db, AsyncBaseDb)
@@ -353,11 +341,14 @@ class Workflow:
353
341
  def _initialize_session_state(
354
342
  self,
355
343
  session_state: Dict[str, Any],
356
- user_id: Optional[str] = None,
357
344
  session_id: Optional[str] = None,
345
+ user_id: Optional[str] = None,
358
346
  run_id: Optional[str] = None,
359
347
  ) -> Dict[str, Any]:
360
348
  """Initialize the session state for the workflow."""
349
+ session_state["workflow_id"] = self.id
350
+ if self.name:
351
+ session_state["workflow_name"] = self.name
361
352
  if user_id:
362
353
  session_state["current_user_id"] = user_id
363
354
  if session_id is not None:
@@ -365,16 +356,6 @@ class Workflow:
365
356
  if run_id is not None:
366
357
  session_state["current_run_id"] = run_id
367
358
 
368
- session_state.update(
369
- {
370
- "workflow_id": self.id,
371
- "run_id": run_id,
372
- "session_id": session_id,
373
- }
374
- )
375
- if self.name:
376
- session_state["workflow_name"] = self.name
377
-
378
359
  return session_state
379
360
 
380
361
  def _generate_workflow_session_name(self) -> str:
@@ -555,6 +536,313 @@ class Workflow:
555
536
  # -*- Delete session
556
537
  self.db.delete_session(session_id=session_id)
557
538
 
539
+ # -*- Serialization Functions
540
+ def to_dict(self) -> Dict[str, Any]:
541
+ """
542
+ Convert the Workflow to a dictionary.
543
+
544
+ Returns:
545
+ Dict[str, Any]: Dictionary representation of the workflow configuration
546
+ """
547
+ config: Dict[str, Any] = {}
548
+
549
+ # --- Workflow settings ---
550
+ if self.name is not None:
551
+ config["name"] = self.name
552
+ if self.id is not None:
553
+ config["id"] = self.id
554
+ if self.description is not None:
555
+ config["description"] = self.description
556
+
557
+ # --- User settings ---
558
+ if self.user_id is not None:
559
+ config["user_id"] = self.user_id
560
+
561
+ # --- Session settings ---
562
+ if self.session_id is not None:
563
+ config["session_id"] = self.session_id
564
+ if self.session_state is not None:
565
+ config["session_state"] = self.session_state
566
+ config["overwrite_db_session_state"] = self.overwrite_db_session_state
567
+
568
+ # --- Database settings ---
569
+ if self.db is not None and hasattr(self.db, "to_dict"):
570
+ config["db"] = self.db.to_dict()
571
+
572
+ # --- History settings ---
573
+ config["add_workflow_history_to_steps"] = self.add_workflow_history_to_steps
574
+ config["num_history_runs"] = self.num_history_runs
575
+
576
+ # --- Streaming settings ---
577
+ if self.stream is not None:
578
+ config["stream"] = self.stream
579
+ config["stream_events"] = self.stream_events
580
+ config["stream_executor_events"] = self.stream_executor_events
581
+ config["store_events"] = self.store_events
582
+ config["store_executor_outputs"] = self.store_executor_outputs
583
+
584
+ # --- Schema settings ---
585
+ if self.input_schema is not None:
586
+ if isinstance(self.input_schema, type) and issubclass(self.input_schema, BaseModel):
587
+ config["input_schema"] = self.input_schema.__name__
588
+ elif isinstance(self.input_schema, dict):
589
+ config["input_schema"] = self.input_schema
590
+
591
+ # --- Metadata ---
592
+ if self.metadata is not None:
593
+ config["metadata"] = self.metadata
594
+
595
+ # --- Debug and telemetry settings ---
596
+ config["debug_mode"] = self.debug_mode
597
+ config["telemetry"] = self.telemetry
598
+
599
+ # --- Steps ---
600
+ # TODO: Implement steps serialization for step types other than Step
601
+ if self.steps and isinstance(self.steps, list):
602
+ config["steps"] = [step.to_dict() for step in self.steps if hasattr(step, "to_dict")]
603
+
604
+ return config
605
+
606
+ @classmethod
607
+ def from_dict(
608
+ cls,
609
+ data: Dict[str, Any],
610
+ db: Optional["BaseDb"] = None,
611
+ links: Optional[List[Dict[str, Any]]] = None,
612
+ registry: Optional[Registry] = None,
613
+ ) -> "Workflow":
614
+ """
615
+ Create a Workflow from a dictionary.
616
+
617
+ Args:
618
+ data: Dictionary containing workflow configuration
619
+ db: Optional database for loading agents/teams in steps
620
+ links: Optional links for this workflow version
621
+ registry: Optional registry for rehydrating executors
622
+
623
+ Returns:
624
+ Workflow: Reconstructed workflow instance
625
+ """
626
+ config = data.copy()
627
+
628
+ # --- Handle DB reconstruction ---
629
+ if "db" in config and isinstance(config["db"], dict):
630
+ db_data = config["db"]
631
+ db_id = db_data.get("id")
632
+
633
+ # First try to get the db from the registry (preferred - reuses existing connection)
634
+ if registry and db_id:
635
+ registry_db = registry.get_db(db_id)
636
+ if registry_db is not None:
637
+ config["db"] = registry_db
638
+ else:
639
+ del config["db"]
640
+ else:
641
+ # No registry or no db_id, fall back to creating from dict
642
+ config["db"] = db_from_dict(db_data)
643
+ if config["db"] is None:
644
+ del config["db"]
645
+
646
+ # --- Handle Schema reconstruction ---
647
+ if "input_schema" in config and isinstance(config["input_schema"], str):
648
+ schema_cls = registry.get_schema(config["input_schema"]) if registry else None
649
+ if schema_cls:
650
+ config["input_schema"] = schema_cls
651
+ else:
652
+ log_warning(f"Input schema {config['input_schema']} not found in registry, skipping.")
653
+ del config["input_schema"]
654
+
655
+ # --- Handle steps reconstruction ---
656
+ steps: Optional[WorkflowSteps] = None
657
+ if "steps" in config and config["steps"]:
658
+ steps = [Step.from_dict(step_data, db=db, links=links, registry=registry) for step_data in config["steps"]]
659
+ del config["steps"]
660
+
661
+ return cls(
662
+ # --- Workflow settings ---
663
+ name=config.get("name"),
664
+ id=config.get("id"),
665
+ description=config.get("description"),
666
+ # --- User settings ---
667
+ user_id=config.get("user_id"),
668
+ # --- Session settings ---
669
+ session_id=config.get("session_id"),
670
+ session_state=config.get("session_state"),
671
+ overwrite_db_session_state=config.get("overwrite_db_session_state", False),
672
+ # --- Database settings ---
673
+ db=config.get("db"),
674
+ # --- History settings ---
675
+ add_workflow_history_to_steps=config.get("add_workflow_history_to_steps", False),
676
+ num_history_runs=config.get("num_history_runs", 3),
677
+ # --- Streaming settings ---
678
+ stream=config.get("stream"),
679
+ stream_events=config.get("stream_events", False),
680
+ stream_executor_events=config.get("stream_executor_events", True),
681
+ store_events=config.get("store_events", False),
682
+ store_executor_outputs=config.get("store_executor_outputs", True),
683
+ # --- Schema settings ---
684
+ # input_schema=config.get("input_schema"), # TODO
685
+ # --- Metadata ---
686
+ metadata=config.get("metadata"),
687
+ # --- Debug and telemetry settings ---
688
+ debug_mode=config.get("debug_mode", False),
689
+ telemetry=config.get("telemetry", True),
690
+ # --- Steps ---
691
+ steps=steps,
692
+ )
693
+
694
+ def save(
695
+ self,
696
+ *,
697
+ db: Optional["BaseDb"] = None,
698
+ stage: str = "published",
699
+ label: Optional[str] = None,
700
+ notes: Optional[str] = None,
701
+ ) -> Optional[int]:
702
+ """
703
+ Save the workflow component and config.
704
+
705
+ Args:
706
+ db: The database to save the component and config to.
707
+ stage: The stage of the component. Defaults to "published".
708
+ label: The label of the component.
709
+ notes: The notes of the component.
710
+
711
+ Returns:
712
+ Optional[int]: The version number of the saved config.
713
+ """
714
+ db_ = db or self.db
715
+ if not db_:
716
+ raise ValueError("Db not initialized or provided")
717
+ if not isinstance(db_, BaseDb):
718
+ raise ValueError("Async databases not yet supported for save(). Use a sync database.")
719
+ if self.id is None:
720
+ self.id = generate_id_from_name(self.name)
721
+
722
+ # Track saved entity versions for pinning links
723
+ saved_versions: Dict[str, int] = {}
724
+
725
+ # Collect all links
726
+ all_links = []
727
+ steps_config = []
728
+
729
+ try:
730
+ steps_to_save = self.steps if isinstance(self.steps, list) else []
731
+ for position, step in enumerate(steps_to_save):
732
+ # TODO: Support other Step types
733
+ if isinstance(step, Step):
734
+ # TODO: Allow not saving a new config if the agent/team already has a published config and no changes have been made
735
+ # Save agent/team if present and capture version
736
+ if step.agent and isinstance(step.agent, Agent):
737
+ agent_version = step.agent.save(
738
+ db=db_,
739
+ stage=stage,
740
+ label=label,
741
+ notes=notes,
742
+ )
743
+ if step.agent.id is not None and agent_version is not None:
744
+ saved_versions[step.agent.id] = agent_version
745
+
746
+ if step.team and isinstance(step.team, Team):
747
+ team_version = step.team.save(db=db_, stage=stage, label=label, notes=notes)
748
+ if step.team.id is not None and team_version is not None:
749
+ saved_versions[step.team.id] = team_version
750
+
751
+ # Add step config
752
+ steps_config.append(step.to_dict())
753
+
754
+ # Add links with position and pinned version
755
+ for link in step.get_links(position=position):
756
+ # Pin the version if we just saved it
757
+ if link["child_component_id"] in saved_versions:
758
+ link["child_version"] = saved_versions[link["child_component_id"]]
759
+ all_links.append(link)
760
+
761
+ db_.upsert_component(
762
+ component_id=self.id,
763
+ component_type=ComponentType.WORKFLOW,
764
+ name=self.name,
765
+ description=self.description,
766
+ metadata=self.metadata,
767
+ )
768
+ config = db_.upsert_config(
769
+ component_id=self.id,
770
+ config=self.to_dict(),
771
+ links=all_links,
772
+ label=label,
773
+ stage=stage,
774
+ notes=notes,
775
+ )
776
+
777
+ return config.get("version")
778
+
779
+ except Exception as e:
780
+ log_error(f"Error saving workflow: {e}")
781
+ return None
782
+
783
+ @classmethod
784
+ def load(
785
+ cls,
786
+ id: str,
787
+ *,
788
+ db: "BaseDb",
789
+ registry: Optional["Registry"] = None,
790
+ label: Optional[str] = None,
791
+ version: Optional[int] = None,
792
+ ) -> Optional["Workflow"]:
793
+ """
794
+ Load a workflow by id.
795
+
796
+ Args:
797
+ id: The id of the workflow to load.
798
+ db: The database to load the workflow from.
799
+ label: The label of the workflow to load.
800
+
801
+ Returns:
802
+ The workflow loaded from the database or None if not found.
803
+ """
804
+ # TODO: Use db.load_component_graph instead of get_config
805
+ data: Optional[Dict[str, Any]] = db.get_config(component_id=id, label=label, version=version)
806
+ if data is None:
807
+ return None
808
+
809
+ config = data.get("config")
810
+ if config is None:
811
+ return None
812
+
813
+ workflow = cls.from_dict(config, db=db, registry=registry)
814
+
815
+ workflow.id = id
816
+ workflow.db = db
817
+
818
+ return workflow
819
+
820
+ def delete(
821
+ self,
822
+ *,
823
+ db: Optional["BaseDb"] = None,
824
+ hard_delete: bool = False,
825
+ ) -> bool:
826
+ """
827
+ Delete the workflow component.
828
+
829
+ Args:
830
+ db: The database to delete the workflow from.
831
+ hard_delete: Whether to hard delete the workflow.
832
+
833
+ Returns:
834
+ True if the workflow was deleted, False otherwise.
835
+ """
836
+ db_ = db or self.db
837
+ if not db_:
838
+ raise ValueError("Db not initialized or provided")
839
+ if not isinstance(db_, BaseDb):
840
+ raise ValueError("Async databases not yet supported for delete(). Use a sync database.")
841
+ if self.id is None:
842
+ raise ValueError("Cannot delete workflow without an id")
843
+
844
+ return db_.delete_component(component_id=self.id, hard_delete=hard_delete)
845
+
558
846
  async def aget_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[WorkflowRunOutput]:
559
847
  """Get a RunOutput from the database."""
560
848
  if self._workflow_session is not None:
@@ -696,12 +984,17 @@ class Workflow:
696
984
  if workflow_session is None:
697
985
  # Creating new session if none found
698
986
  log_debug(f"Creating new WorkflowSession: {session_id}")
987
+ session_data = {}
988
+ if self.session_state is not None:
989
+ from copy import deepcopy
990
+
991
+ session_data["session_state"] = deepcopy(self.session_state)
699
992
  workflow_session = WorkflowSession(
700
993
  session_id=session_id,
701
994
  workflow_id=self.id,
702
995
  user_id=user_id,
703
996
  workflow_data=self._get_workflow_data(),
704
- session_data={},
997
+ session_data=session_data,
705
998
  metadata=self.metadata,
706
999
  created_at=int(time()),
707
1000
  )
@@ -2744,6 +3037,7 @@ class Workflow:
2744
3037
  execution_input: WorkflowExecutionInput,
2745
3038
  run_context: RunContext,
2746
3039
  stream: bool = False,
3040
+ stream_events: bool = False,
2747
3041
  **kwargs: Any,
2748
3042
  ) -> Union[WorkflowRunOutput, Iterator[WorkflowRunOutputEvent]]:
2749
3043
  """
@@ -2757,7 +3051,7 @@ class Workflow:
2757
3051
  execution_input: The execution input
2758
3052
  run_context: The run context
2759
3053
  stream: Whether to stream the response
2760
- stream_intermediate_steps: Whether to stream intermediate steps
3054
+ stream_events: Whether to stream all events
2761
3055
 
2762
3056
  Returns:
2763
3057
  WorkflowRunOutput if stream=False, Iterator[WorkflowRunOutputEvent] if stream=True
@@ -2769,6 +3063,7 @@ class Workflow:
2769
3063
  execution_input=execution_input,
2770
3064
  run_context=run_context,
2771
3065
  stream=stream,
3066
+ stream_events=stream_events,
2772
3067
  **kwargs,
2773
3068
  )
2774
3069
  else:
@@ -2803,7 +3098,7 @@ class Workflow:
2803
3098
 
2804
3099
  from agno.run.workflow import WorkflowCompletedEvent, WorkflowRunOutputEvent
2805
3100
 
2806
- # Initialize agent with stream_intermediate_steps=True so tool yields events
3101
+ # Initialize agent with stream_events=True so tool yields events
2807
3102
  self._initialize_workflow_agent(session, execution_input, run_context=run_context, stream=stream)
2808
3103
 
2809
3104
  # Build dependencies with workflow context
@@ -2843,8 +3138,8 @@ class Workflow:
2843
3138
  for event in self.agent.run( # type: ignore[union-attr]
2844
3139
  input=agent_input,
2845
3140
  stream=True,
2846
- stream_intermediate_steps=True,
2847
- yield_run_response=True,
3141
+ stream_events=True,
3142
+ yield_run_output=True,
2848
3143
  session_id=session.session_id,
2849
3144
  dependencies=run_context.dependencies, # Pass context dynamically per-run
2850
3145
  session_state=run_context.session_state, # Pass session state dynamically per-run
@@ -3232,8 +3527,8 @@ class Workflow:
3232
3527
  async for event in self.agent.arun( # type: ignore[union-attr]
3233
3528
  input=agent_input,
3234
3529
  stream=True,
3235
- stream_intermediate_steps=True,
3236
- yield_run_response=True,
3530
+ stream_events=True,
3531
+ yield_run_output=True,
3237
3532
  session_id=session.session_id,
3238
3533
  dependencies=run_context.dependencies, # Pass context dynamically per-run
3239
3534
  session_state=run_context.session_state, # Pass session state dynamically per-run
@@ -3525,7 +3820,6 @@ class Workflow:
3525
3820
  files: Optional[List[File]] = None,
3526
3821
  stream: Literal[False] = False,
3527
3822
  stream_events: Optional[bool] = None,
3528
- stream_intermediate_steps: Optional[bool] = None,
3529
3823
  background: Optional[bool] = False,
3530
3824
  background_tasks: Optional[Any] = None,
3531
3825
  ) -> WorkflowRunOutput: ...
@@ -3545,7 +3839,6 @@ class Workflow:
3545
3839
  files: Optional[List[File]] = None,
3546
3840
  stream: Literal[True] = True,
3547
3841
  stream_events: Optional[bool] = None,
3548
- stream_intermediate_steps: Optional[bool] = None,
3549
3842
  background: Optional[bool] = False,
3550
3843
  background_tasks: Optional[Any] = None,
3551
3844
  ) -> Iterator[WorkflowRunOutputEvent]: ...
@@ -3564,7 +3857,6 @@ class Workflow:
3564
3857
  files: Optional[List[File]] = None,
3565
3858
  stream: bool = False,
3566
3859
  stream_events: Optional[bool] = None,
3567
- stream_intermediate_steps: Optional[bool] = None,
3568
3860
  background: Optional[bool] = False,
3569
3861
  background_tasks: Optional[Any] = None,
3570
3862
  **kwargs: Any,
@@ -3593,23 +3885,25 @@ class Workflow:
3593
3885
  workflow_session = self.read_or_create_session(session_id=session_id, user_id=user_id)
3594
3886
  self._update_metadata(session=workflow_session)
3595
3887
 
3596
- # Initialize session state
3597
- session_state = self._initialize_session_state(
3888
+ # Initialize session state. Get it from DB if relevant.
3889
+ session_state = self._load_session_state(
3890
+ session=workflow_session,
3598
3891
  session_state=session_state if session_state is not None else {},
3599
- user_id=user_id,
3892
+ )
3893
+
3894
+ # Add current session/user/run info to session_state
3895
+ session_state = self._initialize_session_state(
3896
+ session_state=session_state,
3600
3897
  session_id=session_id,
3898
+ user_id=user_id,
3601
3899
  run_id=run_id,
3602
3900
  )
3603
- # Update session state from DB
3604
- session_state = self._load_session_state(session=workflow_session, session_state=session_state)
3605
3901
 
3606
3902
  log_debug(f"Workflow Run Start: {self.name}", center=True)
3607
3903
 
3608
3904
  # Use simple defaults
3609
3905
  stream = stream or self.stream or False
3610
- stream_events = (stream_events or stream_intermediate_steps) or (
3611
- self.stream_events or self.stream_intermediate_steps
3612
- )
3906
+ stream_events = stream_events or self.stream_events
3613
3907
 
3614
3908
  # Can't stream events if streaming is disabled
3615
3909
  if stream is False:
@@ -3641,6 +3935,8 @@ class Workflow:
3641
3935
  session_id=session_id,
3642
3936
  user_id=user_id,
3643
3937
  session_state=session_state,
3938
+ workflow_id=self.id,
3939
+ workflow_name=self.name,
3644
3940
  )
3645
3941
 
3646
3942
  # Execute workflow agent if configured
@@ -3651,6 +3947,7 @@ class Workflow:
3651
3947
  execution_input=inputs,
3652
3948
  run_context=run_context,
3653
3949
  stream=stream,
3950
+ stream_events=stream_events,
3654
3951
  **kwargs,
3655
3952
  )
3656
3953
 
@@ -3704,7 +4001,6 @@ class Workflow:
3704
4001
  files: Optional[List[File]] = None,
3705
4002
  stream: Literal[False] = False,
3706
4003
  stream_events: Optional[bool] = None,
3707
- stream_intermediate_steps: Optional[bool] = None,
3708
4004
  background: Optional[bool] = False,
3709
4005
  websocket: Optional[WebSocket] = None,
3710
4006
  background_tasks: Optional[Any] = None,
@@ -3725,7 +4021,6 @@ class Workflow:
3725
4021
  files: Optional[List[File]] = None,
3726
4022
  stream: Literal[True] = True,
3727
4023
  stream_events: Optional[bool] = None,
3728
- stream_intermediate_steps: Optional[bool] = None,
3729
4024
  background: Optional[bool] = False,
3730
4025
  websocket: Optional[WebSocket] = None,
3731
4026
  background_tasks: Optional[Any] = None,
@@ -3745,7 +4040,6 @@ class Workflow:
3745
4040
  files: Optional[List[File]] = None,
3746
4041
  stream: bool = False,
3747
4042
  stream_events: Optional[bool] = None,
3748
- stream_intermediate_steps: Optional[bool] = False,
3749
4043
  background: Optional[bool] = False,
3750
4044
  websocket: Optional[WebSocket] = None,
3751
4045
  background_tasks: Optional[Any] = None,
@@ -3766,15 +4060,7 @@ class Workflow:
3766
4060
 
3767
4061
  if background:
3768
4062
  if stream and websocket:
3769
- # Consider both stream_events and stream_intermediate_steps (deprecated)
3770
- if stream_intermediate_steps is not None:
3771
- warnings.warn(
3772
- "The 'stream_intermediate_steps' parameter is deprecated and will be removed in future versions. Use 'stream_events' instead.",
3773
- DeprecationWarning,
3774
- stacklevel=2,
3775
- )
3776
- stream_events = stream_events or stream_intermediate_steps or False
3777
-
4063
+ stream_events = stream_events or False
3778
4064
  # Background + Streaming + WebSocket = Real-time events
3779
4065
  return self._arun_background_stream( # type: ignore
3780
4066
  input=input,
@@ -3828,9 +4114,7 @@ class Workflow:
3828
4114
 
3829
4115
  # Use simple defaults
3830
4116
  stream = stream or self.stream or False
3831
- stream_events = (stream_events or stream_intermediate_steps) or (
3832
- self.stream_events or self.stream_intermediate_steps
3833
- )
4117
+ stream_events = stream_events or self.stream_events
3834
4118
 
3835
4119
  # Can't stream events if streaming is disabled
3836
4120
  if stream is False:
@@ -4096,7 +4380,8 @@ class Workflow:
4096
4380
  **kwargs,
4097
4381
  )
4098
4382
 
4099
- def to_dict(self) -> Dict[str, Any]:
4383
+ # TODO: This is a temporary method to convert the workflow to a dictionary for steps. We need to find a better way to do this.
4384
+ def to_dict_for_steps(self) -> Dict[str, Any]:
4100
4385
  """Convert workflow to dictionary representation"""
4101
4386
 
4102
4387
  def serialize_step(step):
@@ -4481,3 +4766,280 @@ class Workflow:
4481
4766
  session_id=session_id,
4482
4767
  **kwargs,
4483
4768
  )
4769
+
4770
+ def deep_copy(self, *, update: Optional[Dict[str, Any]] = None) -> "Workflow":
4771
+ """Create and return a deep copy of this Workflow, optionally updating fields.
4772
+
4773
+ This creates a fresh Workflow instance with isolated mutable state while sharing
4774
+ heavy resources like database connections. Steps containing agents/teams are also
4775
+ deep copied to ensure complete isolation.
4776
+
4777
+ Args:
4778
+ update: Optional dictionary of fields to override in the new Workflow.
4779
+
4780
+ Returns:
4781
+ Workflow: A new Workflow instance with copied state.
4782
+ """
4783
+ from copy import copy, deepcopy
4784
+ from dataclasses import fields
4785
+
4786
+ from agno.utils.log import log_debug, log_warning
4787
+
4788
+ # Extract the fields to set for the new Workflow
4789
+ fields_for_new_workflow: Dict[str, Any] = {}
4790
+
4791
+ for f in fields(self):
4792
+ # Skip private fields (not part of __init__ signature)
4793
+ if f.name.startswith("_"):
4794
+ continue
4795
+
4796
+ field_value = getattr(self, f.name)
4797
+ if field_value is not None:
4798
+ # Special handling for steps that may contain agents/teams
4799
+ if f.name == "steps" and field_value is not None:
4800
+ fields_for_new_workflow[f.name] = self._deep_copy_steps(field_value)
4801
+ # Special handling for workflow agent
4802
+ elif f.name == "agent" and field_value is not None:
4803
+ if hasattr(field_value, "deep_copy"):
4804
+ fields_for_new_workflow[f.name] = field_value.deep_copy()
4805
+ else:
4806
+ fields_for_new_workflow[f.name] = field_value
4807
+ # Share heavy resources - these maintain connections/pools that shouldn't be duplicated
4808
+ elif f.name == "db":
4809
+ fields_for_new_workflow[f.name] = field_value
4810
+ # For compound types, attempt a deep copy
4811
+ elif isinstance(field_value, (list, dict, set)):
4812
+ try:
4813
+ fields_for_new_workflow[f.name] = deepcopy(field_value)
4814
+ except Exception:
4815
+ try:
4816
+ fields_for_new_workflow[f.name] = copy(field_value)
4817
+ except Exception as e:
4818
+ log_warning(f"Failed to copy field: {f.name} - {e}")
4819
+ fields_for_new_workflow[f.name] = field_value
4820
+ # For pydantic models, attempt a model_copy
4821
+ elif isinstance(field_value, BaseModel):
4822
+ try:
4823
+ fields_for_new_workflow[f.name] = field_value.model_copy(deep=True)
4824
+ except Exception:
4825
+ try:
4826
+ fields_for_new_workflow[f.name] = field_value.model_copy(deep=False)
4827
+ except Exception:
4828
+ fields_for_new_workflow[f.name] = field_value
4829
+ # For other types, attempt a shallow copy
4830
+ else:
4831
+ try:
4832
+ fields_for_new_workflow[f.name] = copy(field_value)
4833
+ except Exception:
4834
+ fields_for_new_workflow[f.name] = field_value
4835
+
4836
+ # Update fields if provided
4837
+ if update:
4838
+ fields_for_new_workflow.update(update)
4839
+
4840
+ # Create a new Workflow
4841
+ try:
4842
+ new_workflow = self.__class__(**fields_for_new_workflow)
4843
+ log_debug(f"Created new {self.__class__.__name__}")
4844
+ return new_workflow
4845
+ except Exception as e:
4846
+ from agno.utils.log import log_error
4847
+
4848
+ log_error(f"Failed to create deep copy of {self.__class__.__name__}: {e}")
4849
+ raise
4850
+
4851
+ def _deep_copy_steps(self, steps: Any) -> Any:
4852
+ """Deep copy workflow steps, handling nested agents and teams."""
4853
+ from agno.workflow.steps import Steps
4854
+
4855
+ if steps is None:
4856
+ return None
4857
+
4858
+ # Handle Steps container
4859
+ if isinstance(steps, Steps):
4860
+ copied_steps = []
4861
+ if steps.steps:
4862
+ for step in steps.steps:
4863
+ copied_steps.append(self._deep_copy_single_step(step))
4864
+ return Steps(steps=copied_steps)
4865
+
4866
+ # Handle list of steps
4867
+ if isinstance(steps, list):
4868
+ return [self._deep_copy_single_step(step) for step in steps]
4869
+
4870
+ # Handle callable steps
4871
+ if callable(steps):
4872
+ return steps
4873
+
4874
+ # Handle single step
4875
+ return self._deep_copy_single_step(steps)
4876
+
4877
+ def _deep_copy_single_step(self, step: Any) -> Any:
4878
+ """Deep copy a single step, handling nested agents and teams."""
4879
+ from copy import copy, deepcopy
4880
+
4881
+ from agno.agent import Agent
4882
+ from agno.team import Team
4883
+ from agno.workflow.condition import Condition
4884
+ from agno.workflow.loop import Loop
4885
+ from agno.workflow.parallel import Parallel
4886
+ from agno.workflow.router import Router
4887
+ from agno.workflow.step import Step
4888
+ from agno.workflow.steps import Steps
4889
+
4890
+ # Handle Step with agent or team
4891
+ if isinstance(step, Step):
4892
+ step_kwargs: Dict[str, Any] = {}
4893
+ if step.name:
4894
+ step_kwargs["name"] = step.name
4895
+ if step.description:
4896
+ step_kwargs["description"] = step.description
4897
+ if step.executor:
4898
+ step_kwargs["executor"] = step.executor
4899
+ if step.agent:
4900
+ step_kwargs["agent"] = step.agent.deep_copy() if hasattr(step.agent, "deep_copy") else step.agent
4901
+ if step.team:
4902
+ step_kwargs["team"] = step.team.deep_copy() if hasattr(step.team, "deep_copy") else step.team
4903
+ # Copy Step configuration attributes
4904
+ for attr in [
4905
+ "max_retries",
4906
+ "timeout_seconds",
4907
+ "skip_on_failure",
4908
+ "strict_input_validation",
4909
+ "add_workflow_history",
4910
+ "num_history_runs",
4911
+ ]:
4912
+ if hasattr(step, attr):
4913
+ value = getattr(step, attr)
4914
+ # Only include non-default values to avoid overriding defaults
4915
+ if value is not None:
4916
+ step_kwargs[attr] = value
4917
+ return Step(**step_kwargs)
4918
+
4919
+ # Handle direct Agent
4920
+ if isinstance(step, Agent):
4921
+ return step.deep_copy() if hasattr(step, "deep_copy") else step
4922
+
4923
+ # Handle direct Team
4924
+ if isinstance(step, Team):
4925
+ return step.deep_copy() if hasattr(step, "deep_copy") else step
4926
+
4927
+ # Handle Parallel steps
4928
+ if isinstance(step, Parallel):
4929
+ copied_parallel_steps = [self._deep_copy_single_step(s) for s in step.steps] if step.steps else []
4930
+ return Parallel(*copied_parallel_steps, name=step.name, description=step.description)
4931
+
4932
+ # Handle Loop steps
4933
+ if isinstance(step, Loop):
4934
+ copied_loop_steps = [self._deep_copy_single_step(s) for s in step.steps] if step.steps else []
4935
+ return Loop(
4936
+ steps=copied_loop_steps,
4937
+ name=step.name,
4938
+ description=step.description,
4939
+ max_iterations=step.max_iterations,
4940
+ end_condition=step.end_condition,
4941
+ )
4942
+
4943
+ # Handle Condition steps
4944
+ if isinstance(step, Condition):
4945
+ copied_condition_steps = [self._deep_copy_single_step(s) for s in step.steps] if step.steps else []
4946
+ return Condition(
4947
+ evaluator=step.evaluator, steps=copied_condition_steps, name=step.name, description=step.description
4948
+ )
4949
+
4950
+ # Handle Router steps
4951
+ if isinstance(step, Router):
4952
+ copied_choices = [self._deep_copy_single_step(s) for s in step.choices] if step.choices else []
4953
+ return Router(choices=copied_choices, name=step.name, description=step.description, selector=step.selector)
4954
+
4955
+ # Handle Steps container
4956
+ if isinstance(step, Steps):
4957
+ copied_steps = [self._deep_copy_single_step(s) for s in step.steps] if step.steps else []
4958
+ return Steps(name=step.name, description=step.description, steps=copied_steps)
4959
+
4960
+ # For other types, attempt deep copy
4961
+ try:
4962
+ return deepcopy(step)
4963
+ except Exception:
4964
+ try:
4965
+ return copy(step)
4966
+ except Exception:
4967
+ return step
4968
+
4969
+
4970
+ def get_workflow_by_id(
4971
+ db: "BaseDb",
4972
+ id: str,
4973
+ version: Optional[int] = None,
4974
+ label: Optional[str] = None,
4975
+ registry: Optional["Registry"] = None,
4976
+ ) -> Optional["Workflow"]:
4977
+ """
4978
+ Get a Workflow by id from the database (new entities/configs schema).
4979
+
4980
+ Resolution order:
4981
+ - if version is provided: load that version
4982
+ - elif label is provided: load that labeled version
4983
+ - else: load entity.current_version
4984
+
4985
+ Args:
4986
+ db: Database handle.
4987
+ id: Workflow entity_id.
4988
+ version: Optional integer config version.
4989
+ label: Optional version_label.
4990
+ registry: Optional Registry for reconstructing unserializable components.
4991
+
4992
+ Returns:
4993
+ Workflow instance or None.
4994
+ """
4995
+ try:
4996
+ row = db.get_config(component_id=id, version=version, label=label)
4997
+ if row is None:
4998
+ return None
4999
+
5000
+ cfg = row.get("config") if isinstance(row, dict) else None
5001
+ if cfg is None:
5002
+ raise ValueError(f"Invalid config found for workflow {id}")
5003
+
5004
+ resolved_version = row.get("version")
5005
+
5006
+ # Get links for this workflow version
5007
+ links = db.get_links(component_id=id, version=resolved_version) if resolved_version else []
5008
+
5009
+ workflow = Workflow.from_dict(cfg, db=db, links=links, registry=registry)
5010
+
5011
+ # Ensure workflow.id is set to the component_id
5012
+ workflow.id = id
5013
+
5014
+ return workflow
5015
+
5016
+ except Exception as e:
5017
+ log_error(f"Error loading Workflow {id} from database: {e}")
5018
+ return None
5019
+
5020
+
5021
+ def get_workflows(
5022
+ db: "BaseDb",
5023
+ registry: Optional["Registry"] = None,
5024
+ ) -> List["Workflow"]:
5025
+ """Get all workflows from the database"""
5026
+ workflows: List[Workflow] = []
5027
+ try:
5028
+ components, _ = db.list_components(component_type=ComponentType.WORKFLOW)
5029
+ for component in components:
5030
+ config = db.get_config(component_id=component["component_id"])
5031
+ if config is not None:
5032
+ workflow_config = config.get("config")
5033
+ if workflow_config is not None:
5034
+ component_id = component["component_id"]
5035
+ if "id" not in workflow_config:
5036
+ workflow_config["id"] = component_id
5037
+ workflow = Workflow.from_dict(workflow_config, db=db, registry=registry)
5038
+ # Ensure workflow.id is set to the component_id
5039
+ workflow.id = component_id
5040
+ workflows.append(workflow)
5041
+ return workflows
5042
+
5043
+ except Exception as e:
5044
+ log_error(f"Error loading Workflows from database: {e}")
5045
+ return []