Repository: agno-agi/agno
Pull request: [Fix] Fix issues in Workflows when loading sessions with new default memory
## Summary

Fix issues in Workflows when loading sessions with new default memory

Fixes #3046 

## Type of change

- [x] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Improvement
- [ ] Model update
- [ ] Other:

---

## Checklist

- [ ] Code complies with style guidelines
- [ ] Ran format/validation scripts (`./scripts/format.sh` and `./scripts/validate.sh`)
- [ ] Self-review completed
- [ ] Documentation updated (comments, docstrings)
- [ ] Examples and guides: Relevant cookbook examples have been included or updated (if applicable)
- [ ] Tested in clean environment
- [ ] Tests added/updated (if applicable)

---

## Additional Notes

Add any important context (deployment instructions, screenshots, security considerations, etc.)


Problem statement:
[Bug]
# Description
Running a workflow from a previous version of agno fails on new version. It seems workflow session_ids cannot be re-used after upgrading. I'm not sure if this is intended to be supported behavior

## Steps to Reproduce
1. Run a workflow with an agno version < 1.4 (before the new memories were implemented)
2. Upgrade agno to the latest
3. Run the workflow again with the same session id
5. Call pprint_run_response() on the workflow
 
## Agent Configuration (if applicable)
N/A

## Expected Behavior
The run_response to print as shown in the docs

## Actual Behavior
An error was thrown

## Screenshots or Logs (if applicable)
```
pprint_run_response(res)
File "/usr/local/lib/python3.12/site-packages/agno/utils/pprint.py", line 54, in pprint_run_response
for resp in run_response:
^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/agno/workflow/workflow.py", line 202, in result_generator
self.memory.add_run(session_id=self.session_id, run=self.run_response) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/agno/memory/v2/memory.py", line 672, in add_run
self.runs.setdefault(session_id, []).append(run)
^^^^^^^^^^^^^^^^^^^^
AttributeError: 'list' object has no attribute 'setdefault'
```

## Environment
- OS: (debian)
- Agno Version: 1.4.3
- Additional Environment Details: (e.g., Python 3.12)

## Additional Context
N/A

Hints from issue discussion:

Commit message: Update

Patch to review:
diff --git a/cookbook/workflows/team_workflow.py b/cookbook/workflows/team_workflow.py
index e7f6152ad3..8ed3c7f65e 100644
--- a/cookbook/workflows/team_workflow.py
+++ b/cookbook/workflows/team_workflow.py
@@ -3,6 +3,7 @@
 
 from agno.agent import Agent, RunResponse
 from agno.models.openai import OpenAIChat
+from agno.run.team import TeamRunResponse
 from agno.team.team import Team
 from agno.tools.duckduckgo import DuckDuckGoTools
 from agno.tools.exa import ExaTools
@@ -87,7 +88,7 @@ class TeamWorkflow(Workflow):
 
     def run(self) -> Iterator[RunResponse]:
         logger.info("Getting top stories from HackerNews.")
-        discussion: RunResponse = self.agent_team.run(
+        discussion: TeamRunResponse = self.agent_team.run(
             "Getting 2 top stories from HackerNews and reddit and write a brief report on them"
         )
         if discussion is None or not discussion.content:
diff --git a/libs/agno/agno/agent/agent.py b/libs/agno/agno/agent/agent.py
index c014236641..22db1bcfe9 100644
--- a/libs/agno/agno/agent/agent.py
+++ b/libs/agno/agno/agent/agent.py
@@ -2019,7 +2019,7 @@ def add_tools_to_model(
                                     func.tool_hooks = self.tool_hooks
                                 _functions_for_model[name] = func
                                 _tools_for_model.append({"type": "function", "function": func.to_dict()})
-                                log_debug(f"Added function {name} from {tool.name}")
+                                log_debug(f"Added tool {name} from {tool.name}")
 
                         # Add instructions from the toolkit
                         if tool.add_instructions and tool.instructions is not None:
@@ -2037,7 +2037,7 @@ def add_tools_to_model(
                                 tool.tool_hooks = self.tool_hooks
                             _functions_for_model[tool.name] = tool
                             _tools_for_model.append({"type": "function", "function": tool.to_dict()})
-                            log_debug(f"Added function {tool.name}")
+                            log_debug(f"Added tool {tool.name}")
 
                         # Add instructions from the Function
                         if tool.add_instructions and tool.instructions is not None:
@@ -2057,10 +2057,11 @@ def add_tools_to_model(
                                     func.tool_hooks = self.tool_hooks
                                 _functions_for_model[func.name] = func
                                 _tools_for_model.append({"type": "function", "function": func.to_dict()})
-                                log_debug(f"Added function {func.name}")
+                                log_debug(f"Added tool {func.name}")
                         except Exception as e:
-                            log_warning(f"Could not add function {tool}: {e}")
+                            log_warning(f"Could not add tool {tool}: {e}")
 
+                log_debug(f"Available tools: {list(_functions_for_model.keys())}")
                 # Set tools on the model
                 model.set_tools(tools=_tools_for_model)
                 # Set functions on the model
diff --git a/libs/agno/agno/team/team.py b/libs/agno/agno/team/team.py
index f4bd792b05..63fed0e279 100644
--- a/libs/agno/agno/team/team.py
+++ b/libs/agno/agno/team/team.py
@@ -475,9 +475,6 @@ def initialize_team(self, session_id: Optional[str] = None) -> None:
         # Set debug mode
         self._set_debug()
 
-        # Make sure for the team, we are using the team logger
-        use_team_logger()
-
         # Set monitoring and telemetry
         self._set_monitoring()
 
@@ -493,6 +490,9 @@ def initialize_team(self, session_id: Optional[str] = None) -> None:
         for member in self.members:
             self._initialize_member(member, session_id=session_id)
 
+        # Make sure for the team, we are using the team logger
+        use_team_logger()
+
     @property
     def is_streamable(self) -> bool:
         return self.response_model is None
@@ -4336,7 +4336,7 @@ def _add_tools_to_model(self, model: Model, tools: List[Union[Function, Callable
                                 func.tool_hooks = self.tool_hooks
                             _functions_for_model[name] = func
                             _tools_for_model.append({"type": "function", "function": func.to_dict()})
-                            log_debug(f"Added function {name} from {tool.name}")
+                            log_debug(f"Added tool {name} from {tool.name}")
 
                     # Add instructions from the toolkit
                     if tool.add_instructions and tool.instructions is not None:
@@ -4355,7 +4355,7 @@ def _add_tools_to_model(self, model: Model, tools: List[Union[Function, Callable
                             tool.tool_hooks = self.tool_hooks
                         _functions_for_model[tool.name] = tool
                         _tools_for_model.append({"type": "function", "function": tool.to_dict()})
-                        log_debug(f"Added function {tool.name}")
+                        log_debug(f"Added tool {tool.name}")
 
                     # Add instructions from the Function
                     if tool.add_instructions and tool.instructions is not None:
@@ -4375,9 +4375,11 @@ def _add_tools_to_model(self, model: Model, tools: List[Union[Function, Callable
                             func.tool_hooks = self.tool_hooks
                         _functions_for_model[func.name] = func
                         _tools_for_model.append({"type": "function", "function": func.to_dict()})
-                        log_debug(f"Added function {func.name}")
+                        log_debug(f"Added tool {func.name}")
                     except Exception as e:
-                        log_warning(f"Could not add function {tool}: {e}")
+                        log_warning(f"Could not add tool {tool}: {e}")
+
+            log_debug(f"Available tools: {list(_functions_for_model.keys())}")
 
             # Set tools on the model
             model.set_tools(tools=_tools_for_model)
diff --git a/libs/agno/agno/workflow/workflow.py b/libs/agno/agno/workflow/workflow.py
index c7c70600d9..2609d5850d 100644
--- a/libs/agno/agno/workflow/workflow.py
+++ b/libs/agno/agno/workflow/workflow.py
@@ -18,7 +18,7 @@
 from agno.storage.base import Storage
 from agno.storage.session.workflow import WorkflowSession
 from agno.utils.common import nested_model_dump
-from agno.utils.log import log_debug, logger, set_log_level_to_debug, set_log_level_to_info
+from agno.utils.log import log_debug, log_warning, logger, set_log_level_to_debug, set_log_level_to_info
 from agno.utils.merge_dict import merge_dictionaries
 
 
@@ -358,11 +358,29 @@ def get_workflow_session(self) -> WorkflowSession:
         self.memory = cast(WorkflowMemory, self.memory)
         self.session_id = cast(str, self.session_id)
         self.workflow_id = cast(str, self.workflow_id)
+        if self.memory is not None:
+            if isinstance(self.memory, WorkflowMemory):
+                self.memory = cast(WorkflowMemory, self.memory)
+                memory_dict = self.memory.to_dict()
+                # We only persist the runs for the current session ID (not all runs in memory)
+                memory_dict["runs"] = [
+                    agent_run.to_dict()
+                    for agent_run in self.memory.runs
+                    if agent_run.response is not None and agent_run.response.session_id == self.session_id
+                ]
+            else:
+                self.memory = cast(Memory, self.memory)
+                # We fake the structure on storage, to maintain the interface with the legacy implementation
+                run_responses = self.memory.runs[self.session_id]  # type: ignore
+                memory_dict = self.memory.to_dict()
+                memory_dict["runs"] = [rr.to_dict() for rr in run_responses]
+        else:
+            memory_dict = None
         return WorkflowSession(
             session_id=self.session_id,
             workflow_id=self.workflow_id,
             user_id=self.user_id,
-            memory=self.memory.to_dict() if self.memory is not None else None,
+            memory=memory_dict,
             workflow_data=self.get_workflow_data(),
             session_data=self.get_session_data(),
             extra_data=self.extra_data,
@@ -437,16 +455,28 @@ def load_workflow_session(self, session: WorkflowSession):
 
         if session.memory is not None:
             if self.memory is None:
-                self.memory = WorkflowMemory()
+                self.memory = Memory()
+
+            if isinstance(self.memory, Memory):
+                try:
+                    if self.memory.runs is None:
+                        self.memory.runs = {}
+                    self.memory.runs[session.session_id] = []
+                    for run in session.memory["runs"]:
+                        run_session_id = run["session_id"]
+                        self.memory.runs[run_session_id].append(RunResponse.from_dict(run))
+                except Exception as e:
+                    log_warning(f"Failed to load runs from memory: {e}")
+            else:
+                try:
+                    if "runs" in session.memory:
+                        try:
+                            self.memory.runs = [WorkflowRun(**m) for m in session.memory["runs"]]
+                        except Exception as e:
+                            logger.warning(f"Failed to load runs from memory: {e}")
+                except Exception as e:
+                    logger.warning(f"Failed to load WorkflowMemory: {e}")
 
-            try:
-                if "runs" in session.memory:
-                    try:
-                        self.memory.runs = [WorkflowRun(**m) for m in session.memory["runs"]]
-                    except Exception as e:
-                        logger.warning(f"Failed to load runs from memory: {e}")
-            except Exception as e:
-                logger.warning(f"Failed to load WorkflowMemory: {e}")
         log_debug(f"-*- WorkflowSession loaded: {session.session_id}")
 
     def read_from_storage(self) -> Optional[WorkflowSession]:


Code review comments:
diff_hunk: @@ -2057,10 +2057,11 @@ def add_tools_to_model(
                                     func.tool_hooks = self.tool_hooks
                                 _functions_for_model[func.name] = func
                                 _tools_for_model.append({"type": "function", "function": func.to_dict()})
-                                log_debug(f"Added function {func.name}")
+                                log_debug(f"Added tool {func.name}")
                         except Exception as e:
-                            log_warning(f"Could not add function {tool}: {e}")
+                            log_warning(f"Could not add tool {tool}: {e}")
 
+                log_debug(f"Available tools: {list(_functions_for_model.keys())}")
path: libs/agno/agno/agent/agent.py
text: @user1:
This seems necessary?

@author:
This is very useful to know which tools are available to the model when it is being run

@user1:
Since we already mention `log_debug(f"Added tool {tool.name}")` for each tool it would be duplicated?