agno 2.3.25__py3-none-any.whl → 2.3.26__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.
agno/agent/agent.py CHANGED
@@ -9877,18 +9877,30 @@ class Agent:
9877
9877
  fields_for_new_agent: Dict[str, Any] = {}
9878
9878
 
9879
9879
  for f in fields(self):
9880
+ # Skip private fields (not part of __init__ signature)
9881
+ if f.name.startswith("_"):
9882
+ continue
9883
+
9880
9884
  field_value = getattr(self, f.name)
9881
9885
  if field_value is not None:
9882
- fields_for_new_agent[f.name] = self._deep_copy_field(f.name, field_value)
9886
+ try:
9887
+ fields_for_new_agent[f.name] = self._deep_copy_field(f.name, field_value)
9888
+ except Exception as e:
9889
+ log_warning(f"Failed to deep copy field '{f.name}': {e}. Using original value.")
9890
+ fields_for_new_agent[f.name] = field_value
9883
9891
 
9884
9892
  # Update fields if provided
9885
9893
  if update:
9886
9894
  fields_for_new_agent.update(update)
9887
9895
 
9888
9896
  # Create a new Agent
9889
- new_agent = self.__class__(**fields_for_new_agent)
9890
- log_debug(f"Created new {self.__class__.__name__}")
9891
- return new_agent
9897
+ try:
9898
+ new_agent = self.__class__(**fields_for_new_agent)
9899
+ log_debug(f"Created new {self.__class__.__name__}")
9900
+ return new_agent
9901
+ except Exception as e:
9902
+ log_error(f"Failed to create deep copy of {self.__class__.__name__}: {e}")
9903
+ raise
9892
9904
 
9893
9905
  def _deep_copy_field(self, field_name: str, field_value: Any) -> Any:
9894
9906
  """Helper method to deep copy a field based on its type."""
@@ -9898,19 +9910,52 @@ class Agent:
9898
9910
  if field_name == "reasoning_agent":
9899
9911
  return field_value.deep_copy()
9900
9912
 
9901
- # For storage, model and reasoning_model, use a deep copy
9902
- elif field_name in ("db", "model", "reasoning_model"):
9913
+ # For tools, share MCP tools but copy others
9914
+ if field_name == "tools" and field_value is not None:
9903
9915
  try:
9904
- return deepcopy(field_value)
9905
- except Exception:
9906
- try:
9907
- return copy(field_value)
9908
- except Exception as e:
9909
- log_warning(f"Failed to copy field: {field_name} - {e}")
9910
- return field_value
9916
+ copied_tools = []
9917
+ for tool in field_value:
9918
+ try:
9919
+ # Share MCP tools (they maintain server connections)
9920
+ is_mcp_tool = hasattr(type(tool), "__mro__") and any(
9921
+ c.__name__ in ["MCPTools", "MultiMCPTools"] for c in type(tool).__mro__
9922
+ )
9923
+ if is_mcp_tool:
9924
+ copied_tools.append(tool)
9925
+ else:
9926
+ try:
9927
+ copied_tools.append(deepcopy(tool))
9928
+ except Exception:
9929
+ # Tool can't be deep copied, share by reference
9930
+ copied_tools.append(tool)
9931
+ except Exception:
9932
+ # MCP detection failed, share tool by reference to be safe
9933
+ copied_tools.append(tool)
9934
+ return copied_tools
9935
+ except Exception as e:
9936
+ # If entire tools processing fails, log and return original list
9937
+ log_warning(f"Failed to process tools for deep copy: {e}")
9938
+ return field_value
9939
+
9940
+ # Share heavy resources - these maintain connections/pools that shouldn't be duplicated
9941
+ if field_name in (
9942
+ "db",
9943
+ "model",
9944
+ "reasoning_model",
9945
+ "knowledge",
9946
+ "memory_manager",
9947
+ "parser_model",
9948
+ "output_model",
9949
+ "session_summary_manager",
9950
+ "culture_manager",
9951
+ "compression_manager",
9952
+ "learning",
9953
+ "skills",
9954
+ ):
9955
+ return field_value
9911
9956
 
9912
9957
  # For compound types, attempt a deep copy
9913
- elif isinstance(field_value, (list, dict, set)):
9958
+ if isinstance(field_value, (list, dict, set)):
9914
9959
  try:
9915
9960
  return deepcopy(field_value)
9916
9961
  except Exception:
@@ -9921,7 +9966,7 @@ class Agent:
9921
9966
  return field_value
9922
9967
 
9923
9968
  # For pydantic models, attempt a model_copy
9924
- elif isinstance(field_value, BaseModel):
9969
+ if isinstance(field_value, BaseModel):
9925
9970
  try:
9926
9971
  return field_value.model_copy(deep=True)
9927
9972
  except Exception:
@@ -9933,8 +9978,6 @@ class Agent:
9933
9978
 
9934
9979
  # For other types, attempt a shallow copy first
9935
9980
  try:
9936
- from copy import copy
9937
-
9938
9981
  return copy(field_value)
9939
9982
  except Exception:
9940
9983
  # If copy fails, return as is
@@ -243,7 +243,7 @@ def get_agent_router(
243
243
  log_warning("Metadata parameter passed in both request state and kwargs, using request state")
244
244
  kwargs["metadata"] = metadata
245
245
 
246
- agent = get_agent_by_id(agent_id, os.agents)
246
+ agent = get_agent_by_id(agent_id, os.agents, create_fresh=True)
247
247
  if agent is None:
248
248
  raise HTTPException(status_code=404, detail="Agent not found")
249
249
 
@@ -405,7 +405,7 @@ def get_agent_router(
405
405
  agent_id: str,
406
406
  run_id: str,
407
407
  ):
408
- agent = get_agent_by_id(agent_id, os.agents)
408
+ agent = get_agent_by_id(agent_id, os.agents, create_fresh=True)
409
409
  if agent is None:
410
410
  raise HTTPException(status_code=404, detail="Agent not found")
411
411
 
@@ -464,7 +464,7 @@ def get_agent_router(
464
464
  except json.JSONDecodeError:
465
465
  raise HTTPException(status_code=400, detail="Invalid JSON in tools field")
466
466
 
467
- agent = get_agent_by_id(agent_id, os.agents)
467
+ agent = get_agent_by_id(agent_id, os.agents, create_fresh=True)
468
468
  if agent is None:
469
469
  raise HTTPException(status_code=404, detail="Agent not found")
470
470
 
@@ -630,7 +630,7 @@ def get_agent_router(
630
630
  dependencies=[Depends(require_resource_access("agents", "read", "agent_id"))],
631
631
  )
632
632
  async def get_agent(agent_id: str, request: Request) -> AgentResponse:
633
- agent = get_agent_by_id(agent_id, os.agents)
633
+ agent = get_agent_by_id(agent_id, os.agents, create_fresh=True)
634
634
  if agent is None:
635
635
  raise HTTPException(status_code=404, detail="Agent not found")
636
636
 
@@ -194,7 +194,7 @@ def get_team_router(
194
194
 
195
195
  logger.debug(f"Creating team run: {message=} {session_id=} {monitor=} {user_id=} {team_id=} {files=} {kwargs=}")
196
196
 
197
- team = get_team_by_id(team_id, os.teams)
197
+ team = get_team_by_id(team_id, os.teams, create_fresh=True)
198
198
  if team is None:
199
199
  raise HTTPException(status_code=404, detail="Team not found")
200
200
 
@@ -321,7 +321,7 @@ def get_team_router(
321
321
  team_id: str,
322
322
  run_id: str,
323
323
  ):
324
- team = get_team_by_id(team_id, os.teams)
324
+ team = get_team_by_id(team_id, os.teams, create_fresh=True)
325
325
  if team is None:
326
326
  raise HTTPException(status_code=404, detail="Team not found")
327
327
 
@@ -526,7 +526,7 @@ def get_team_router(
526
526
  dependencies=[Depends(require_resource_access("teams", "read", "team_id"))],
527
527
  )
528
528
  async def get_team(team_id: str, request: Request) -> TeamResponse:
529
- team = get_team_by_id(team_id, os.teams)
529
+ team = get_team_by_id(team_id, os.teams, create_fresh=True)
530
530
  if team is None:
531
531
  raise HTTPException(status_code=404, detail="Team not found")
532
532
 
@@ -61,7 +61,7 @@ async def handle_workflow_via_websocket(websocket: WebSocket, message: dict, os:
61
61
  return
62
62
 
63
63
  # Get workflow from OS
64
- workflow = get_workflow_by_id(workflow_id, os.workflows)
64
+ workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
65
65
  if not workflow:
66
66
  await websocket.send_text(json.dumps({"event": "error", "error": f"Workflow {workflow_id} not found"}))
67
67
  return
@@ -141,7 +141,7 @@ async def handle_workflow_subscription(websocket: WebSocket, message: dict, os:
141
141
  if buffer_status is None:
142
142
  # Run not in buffer - check database
143
143
  if workflow_id and session_id:
144
- workflow = get_workflow_by_id(workflow_id, os.workflows)
144
+ workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
145
145
  if workflow and isinstance(workflow, Workflow):
146
146
  workflow_run = await workflow.aget_run_output(run_id, session_id)
147
147
 
@@ -571,7 +571,7 @@ def get_workflow_router(
571
571
  dependencies=[Depends(require_resource_access("workflows", "read", "workflow_id"))],
572
572
  )
573
573
  async def get_workflow(workflow_id: str, request: Request) -> WorkflowResponse:
574
- workflow = get_workflow_by_id(workflow_id, os.workflows)
574
+ workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
575
575
  if workflow is None:
576
576
  raise HTTPException(status_code=404, detail="Workflow not found")
577
577
  if isinstance(workflow, RemoteWorkflow):
@@ -650,7 +650,7 @@ def get_workflow_router(
650
650
  kwargs["metadata"] = metadata
651
651
 
652
652
  # Retrieve the workflow by ID
653
- workflow = get_workflow_by_id(workflow_id, os.workflows)
653
+ workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
654
654
  if workflow is None:
655
655
  raise HTTPException(status_code=404, detail="Workflow not found")
656
656
 
@@ -716,7 +716,7 @@ def get_workflow_router(
716
716
  dependencies=[Depends(require_resource_access("workflows", "run", "workflow_id"))],
717
717
  )
718
718
  async def cancel_workflow_run(workflow_id: str, run_id: str):
719
- workflow = get_workflow_by_id(workflow_id, os.workflows)
719
+ workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
720
720
 
721
721
  if workflow is None:
722
722
  raise HTTPException(status_code=404, detail="Workflow not found")
agno/os/utils.py CHANGED
@@ -414,37 +414,89 @@ def extract_format(file: UploadFile) -> Optional[str]:
414
414
 
415
415
 
416
416
  def get_agent_by_id(
417
- agent_id: str, agents: Optional[List[Union[Agent, RemoteAgent]]] = None
417
+ agent_id: str,
418
+ agents: Optional[List[Union[Agent, RemoteAgent]]] = None,
419
+ create_fresh: bool = False,
418
420
  ) -> Optional[Union[Agent, RemoteAgent]]:
421
+ """Get an agent by ID, optionally creating a fresh instance for request isolation.
422
+
423
+ When create_fresh=True, creates a new agent instance using deep_copy() to prevent
424
+ state contamination between concurrent requests. The new instance shares heavy
425
+ resources (db, model, MCP tools) but has isolated mutable state.
426
+
427
+ Args:
428
+ agent_id: The agent ID to look up
429
+ agents: List of agents to search
430
+ create_fresh: If True, creates a new instance using deep_copy()
431
+
432
+ Returns:
433
+ The agent instance (shared or fresh copy based on create_fresh)
434
+ """
419
435
  if agent_id is None or agents is None:
420
436
  return None
421
437
 
422
438
  for agent in agents:
423
439
  if agent.id == agent_id:
440
+ if create_fresh and isinstance(agent, Agent):
441
+ return agent.deep_copy()
424
442
  return agent
425
443
  return None
426
444
 
427
445
 
428
446
  def get_team_by_id(
429
- team_id: str, teams: Optional[List[Union[Team, RemoteTeam]]] = None
447
+ team_id: str,
448
+ teams: Optional[List[Union[Team, RemoteTeam]]] = None,
449
+ create_fresh: bool = False,
430
450
  ) -> Optional[Union[Team, RemoteTeam]]:
451
+ """Get a team by ID, optionally creating a fresh instance for request isolation.
452
+
453
+ When create_fresh=True, creates a new team instance using deep_copy() to prevent
454
+ state contamination between concurrent requests. Member agents are also deep copied.
455
+
456
+ Args:
457
+ team_id: The team ID to look up
458
+ teams: List of teams to search
459
+ create_fresh: If True, creates a new instance using deep_copy()
460
+
461
+ Returns:
462
+ The team instance (shared or fresh copy based on create_fresh)
463
+ """
431
464
  if team_id is None or teams is None:
432
465
  return None
433
466
 
434
467
  for team in teams:
435
468
  if team.id == team_id:
469
+ if create_fresh and isinstance(team, Team):
470
+ return team.deep_copy()
436
471
  return team
437
472
  return None
438
473
 
439
474
 
440
475
  def get_workflow_by_id(
441
- workflow_id: str, workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = None
476
+ workflow_id: str,
477
+ workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = None,
478
+ create_fresh: bool = False,
442
479
  ) -> Optional[Union[Workflow, RemoteWorkflow]]:
480
+ """Get a workflow by ID, optionally creating a fresh instance for request isolation.
481
+
482
+ When create_fresh=True, creates a new workflow instance using deep_copy() to prevent
483
+ state contamination between concurrent requests. Steps containing agents/teams are also deep copied.
484
+
485
+ Args:
486
+ workflow_id: The workflow ID to look up
487
+ workflows: List of workflows to search
488
+ create_fresh: If True, creates a new instance using deep_copy()
489
+
490
+ Returns:
491
+ The workflow instance (shared or fresh copy based on create_fresh)
492
+ """
443
493
  if workflow_id is None or workflows is None:
444
494
  return None
445
495
 
446
496
  for workflow in workflows:
447
497
  if workflow.id == workflow_id:
498
+ if create_fresh and isinstance(workflow, Workflow):
499
+ return workflow.deep_copy()
448
500
  return workflow
449
501
  return None
450
502
 
agno/team/team.py CHANGED
@@ -9392,3 +9392,134 @@ class Team:
9392
9392
  )
9393
9393
  except Exception as e:
9394
9394
  log_debug(f"Could not create Team run telemetry event: {e}")
9395
+
9396
+ def deep_copy(self, *, update: Optional[Dict[str, Any]] = None) -> "Team":
9397
+ """Create and return a deep copy of this Team, optionally updating fields.
9398
+
9399
+ This creates a fresh Team instance with isolated mutable state while sharing
9400
+ heavy resources like database connections and models. Member agents are also
9401
+ deep copied to ensure complete isolation.
9402
+
9403
+ Args:
9404
+ update: Optional dictionary of fields to override in the new Team.
9405
+
9406
+ Returns:
9407
+ Team: A new Team instance with copied state.
9408
+ """
9409
+ from dataclasses import fields
9410
+
9411
+ # Extract the fields to set for the new Team
9412
+ fields_for_new_team: Dict[str, Any] = {}
9413
+
9414
+ for f in fields(self):
9415
+ # Skip private fields (not part of __init__ signature)
9416
+ if f.name.startswith("_"):
9417
+ continue
9418
+
9419
+ field_value = getattr(self, f.name)
9420
+ if field_value is not None:
9421
+ try:
9422
+ fields_for_new_team[f.name] = self._deep_copy_field(f.name, field_value)
9423
+ except Exception as e:
9424
+ log_warning(f"Failed to deep copy field '{f.name}': {e}. Using original value.")
9425
+ fields_for_new_team[f.name] = field_value
9426
+
9427
+ # Update fields if provided
9428
+ if update:
9429
+ fields_for_new_team.update(update)
9430
+
9431
+ # Create a new Team
9432
+ try:
9433
+ new_team = self.__class__(**fields_for_new_team)
9434
+ log_debug(f"Created new {self.__class__.__name__}")
9435
+ return new_team
9436
+ except Exception as e:
9437
+ log_error(f"Failed to create deep copy of {self.__class__.__name__}: {e}")
9438
+ raise
9439
+
9440
+ def _deep_copy_field(self, field_name: str, field_value: Any) -> Any:
9441
+ """Helper method to deep copy a field based on its type."""
9442
+ from copy import copy, deepcopy
9443
+
9444
+ # For members, deep copy each agent/team
9445
+ if field_name == "members" and field_value is not None:
9446
+ copied_members = []
9447
+ for member in field_value:
9448
+ if hasattr(member, "deep_copy"):
9449
+ copied_members.append(member.deep_copy())
9450
+ else:
9451
+ copied_members.append(member)
9452
+ return copied_members
9453
+
9454
+ # For tools, share MCP tools but copy others
9455
+ if field_name == "tools" and field_value is not None:
9456
+ try:
9457
+ copied_tools = []
9458
+ for tool in field_value:
9459
+ try:
9460
+ # Share MCP tools (they maintain server connections)
9461
+ is_mcp_tool = hasattr(type(tool), "__mro__") and any(
9462
+ c.__name__ in ["MCPTools", "MultiMCPTools"] for c in type(tool).__mro__
9463
+ )
9464
+ if is_mcp_tool:
9465
+ copied_tools.append(tool)
9466
+ else:
9467
+ try:
9468
+ copied_tools.append(deepcopy(tool))
9469
+ except Exception:
9470
+ # Tool can't be deep copied, share by reference
9471
+ copied_tools.append(tool)
9472
+ except Exception:
9473
+ # MCP detection failed, share tool by reference to be safe
9474
+ copied_tools.append(tool)
9475
+ return copied_tools
9476
+ except Exception as e:
9477
+ # If entire tools processing fails, log and return original list
9478
+ log_warning(f"Failed to process tools for deep copy: {e}")
9479
+ return field_value
9480
+
9481
+ # Share heavy resources - these maintain connections/pools that shouldn't be duplicated
9482
+ if field_name in (
9483
+ "db",
9484
+ "model",
9485
+ "reasoning_model",
9486
+ "knowledge",
9487
+ "memory_manager",
9488
+ "parser_model",
9489
+ "output_model",
9490
+ "session_summary_manager",
9491
+ "culture_manager",
9492
+ "compression_manager",
9493
+ "learning",
9494
+ "skills",
9495
+ ):
9496
+ return field_value
9497
+
9498
+ # For compound types, attempt a deep copy
9499
+ if isinstance(field_value, (list, dict, set)):
9500
+ try:
9501
+ return deepcopy(field_value)
9502
+ except Exception:
9503
+ try:
9504
+ return copy(field_value)
9505
+ except Exception as e:
9506
+ log_warning(f"Failed to copy field: {field_name} - {e}")
9507
+ return field_value
9508
+
9509
+ # For pydantic models, attempt a model_copy
9510
+ if isinstance(field_value, BaseModel):
9511
+ try:
9512
+ return field_value.model_copy(deep=True)
9513
+ except Exception:
9514
+ try:
9515
+ return field_value.model_copy(deep=False)
9516
+ except Exception as e:
9517
+ log_warning(f"Failed to copy field: {field_name} - {e}")
9518
+ return field_value
9519
+
9520
+ # For other types, attempt a shallow copy first
9521
+ try:
9522
+ return copy(field_value)
9523
+ except Exception:
9524
+ # If copy fails, return as is
9525
+ return field_value
agno/workflow/workflow.py CHANGED
@@ -4481,3 +4481,201 @@ class Workflow:
4481
4481
  session_id=session_id,
4482
4482
  **kwargs,
4483
4483
  )
4484
+
4485
+ def deep_copy(self, *, update: Optional[Dict[str, Any]] = None) -> "Workflow":
4486
+ """Create and return a deep copy of this Workflow, optionally updating fields.
4487
+
4488
+ This creates a fresh Workflow instance with isolated mutable state while sharing
4489
+ heavy resources like database connections. Steps containing agents/teams are also
4490
+ deep copied to ensure complete isolation.
4491
+
4492
+ Args:
4493
+ update: Optional dictionary of fields to override in the new Workflow.
4494
+
4495
+ Returns:
4496
+ Workflow: A new Workflow instance with copied state.
4497
+ """
4498
+ from copy import copy, deepcopy
4499
+ from dataclasses import fields
4500
+
4501
+ from agno.utils.log import log_debug, log_warning
4502
+
4503
+ # Extract the fields to set for the new Workflow
4504
+ fields_for_new_workflow: Dict[str, Any] = {}
4505
+
4506
+ for f in fields(self):
4507
+ # Skip private fields (not part of __init__ signature)
4508
+ if f.name.startswith("_"):
4509
+ continue
4510
+
4511
+ field_value = getattr(self, f.name)
4512
+ if field_value is not None:
4513
+ # Special handling for steps that may contain agents/teams
4514
+ if f.name == "steps" and field_value is not None:
4515
+ fields_for_new_workflow[f.name] = self._deep_copy_steps(field_value)
4516
+ # Special handling for workflow agent
4517
+ elif f.name == "agent" and field_value is not None:
4518
+ if hasattr(field_value, "deep_copy"):
4519
+ fields_for_new_workflow[f.name] = field_value.deep_copy()
4520
+ else:
4521
+ fields_for_new_workflow[f.name] = field_value
4522
+ # Share heavy resources - these maintain connections/pools that shouldn't be duplicated
4523
+ elif f.name == "db":
4524
+ fields_for_new_workflow[f.name] = field_value
4525
+ # For compound types, attempt a deep copy
4526
+ elif isinstance(field_value, (list, dict, set)):
4527
+ try:
4528
+ fields_for_new_workflow[f.name] = deepcopy(field_value)
4529
+ except Exception:
4530
+ try:
4531
+ fields_for_new_workflow[f.name] = copy(field_value)
4532
+ except Exception as e:
4533
+ log_warning(f"Failed to copy field: {f.name} - {e}")
4534
+ fields_for_new_workflow[f.name] = field_value
4535
+ # For pydantic models, attempt a model_copy
4536
+ elif isinstance(field_value, BaseModel):
4537
+ try:
4538
+ fields_for_new_workflow[f.name] = field_value.model_copy(deep=True)
4539
+ except Exception:
4540
+ try:
4541
+ fields_for_new_workflow[f.name] = field_value.model_copy(deep=False)
4542
+ except Exception:
4543
+ fields_for_new_workflow[f.name] = field_value
4544
+ # For other types, attempt a shallow copy
4545
+ else:
4546
+ try:
4547
+ fields_for_new_workflow[f.name] = copy(field_value)
4548
+ except Exception:
4549
+ fields_for_new_workflow[f.name] = field_value
4550
+
4551
+ # Update fields if provided
4552
+ if update:
4553
+ fields_for_new_workflow.update(update)
4554
+
4555
+ # Create a new Workflow
4556
+ try:
4557
+ new_workflow = self.__class__(**fields_for_new_workflow)
4558
+ log_debug(f"Created new {self.__class__.__name__}")
4559
+ return new_workflow
4560
+ except Exception as e:
4561
+ from agno.utils.log import log_error
4562
+ log_error(f"Failed to create deep copy of {self.__class__.__name__}: {e}")
4563
+ raise
4564
+
4565
+ def _deep_copy_steps(self, steps: Any) -> Any:
4566
+ """Deep copy workflow steps, handling nested agents and teams."""
4567
+ from agno.workflow.steps import Steps
4568
+
4569
+ if steps is None:
4570
+ return None
4571
+
4572
+ # Handle Steps container
4573
+ if isinstance(steps, Steps):
4574
+ copied_steps = []
4575
+ if steps.steps:
4576
+ for step in steps.steps:
4577
+ copied_steps.append(self._deep_copy_single_step(step))
4578
+ return Steps(steps=copied_steps)
4579
+
4580
+ # Handle list of steps
4581
+ if isinstance(steps, list):
4582
+ return [self._deep_copy_single_step(step) for step in steps]
4583
+
4584
+ # Handle callable steps
4585
+ if callable(steps):
4586
+ return steps
4587
+
4588
+ # Handle single step
4589
+ return self._deep_copy_single_step(steps)
4590
+
4591
+ def _deep_copy_single_step(self, step: Any) -> Any:
4592
+ """Deep copy a single step, handling nested agents and teams."""
4593
+ from copy import copy, deepcopy
4594
+
4595
+ from agno.agent import Agent
4596
+ from agno.team import Team
4597
+ from agno.workflow.condition import Condition
4598
+ from agno.workflow.loop import Loop
4599
+ from agno.workflow.parallel import Parallel
4600
+ from agno.workflow.router import Router
4601
+ from agno.workflow.step import Step
4602
+ from agno.workflow.steps import Steps
4603
+
4604
+ # Handle Step with agent or team
4605
+ if isinstance(step, Step):
4606
+ step_kwargs: Dict[str, Any] = {}
4607
+ if step.name:
4608
+ step_kwargs["name"] = step.name
4609
+ if step.description:
4610
+ step_kwargs["description"] = step.description
4611
+ if step.executor:
4612
+ step_kwargs["executor"] = step.executor
4613
+ if step.agent:
4614
+ step_kwargs["agent"] = step.agent.deep_copy() if hasattr(step.agent, "deep_copy") else step.agent
4615
+ if step.team:
4616
+ step_kwargs["team"] = step.team.deep_copy() if hasattr(step.team, "deep_copy") else step.team
4617
+ # Copy Step configuration attributes
4618
+ for attr in [
4619
+ "max_retries",
4620
+ "timeout_seconds",
4621
+ "skip_on_failure",
4622
+ "strict_input_validation",
4623
+ "add_workflow_history",
4624
+ "num_history_runs",
4625
+ ]:
4626
+ if hasattr(step, attr):
4627
+ value = getattr(step, attr)
4628
+ # Only include non-default values to avoid overriding defaults
4629
+ if value is not None:
4630
+ step_kwargs[attr] = value
4631
+ return Step(**step_kwargs)
4632
+
4633
+ # Handle direct Agent
4634
+ if isinstance(step, Agent):
4635
+ return step.deep_copy() if hasattr(step, "deep_copy") else step
4636
+
4637
+ # Handle direct Team
4638
+ if isinstance(step, Team):
4639
+ return step.deep_copy() if hasattr(step, "deep_copy") else step
4640
+
4641
+ # Handle Parallel steps
4642
+ if isinstance(step, Parallel):
4643
+ copied_parallel_steps = [self._deep_copy_single_step(s) for s in step.steps] if step.steps else []
4644
+ return Parallel(*copied_parallel_steps, name=step.name, description=step.description)
4645
+
4646
+ # Handle Loop steps
4647
+ if isinstance(step, Loop):
4648
+ copied_loop_steps = [self._deep_copy_single_step(s) for s in step.steps] if step.steps else []
4649
+ return Loop(
4650
+ steps=copied_loop_steps,
4651
+ name=step.name,
4652
+ description=step.description,
4653
+ max_iterations=step.max_iterations,
4654
+ end_condition=step.end_condition,
4655
+ )
4656
+
4657
+ # Handle Condition steps
4658
+ if isinstance(step, Condition):
4659
+ copied_condition_steps = [self._deep_copy_single_step(s) for s in step.steps] if step.steps else []
4660
+ return Condition(
4661
+ evaluator=step.evaluator, steps=copied_condition_steps, name=step.name, description=step.description
4662
+ )
4663
+
4664
+ # Handle Router steps
4665
+ if isinstance(step, Router):
4666
+ copied_choices = [self._deep_copy_single_step(s) for s in step.choices] if step.choices else []
4667
+ return Router(choices=copied_choices, name=step.name, description=step.description, selector=step.selector)
4668
+
4669
+ # Handle Steps container
4670
+ if isinstance(step, Steps):
4671
+ copied_steps = [self._deep_copy_single_step(s) for s in step.steps] if step.steps else []
4672
+ return Steps(name=step.name, description=step.description, steps=copied_steps)
4673
+
4674
+ # For other types, attempt deep copy
4675
+ try:
4676
+ return deepcopy(step)
4677
+ except Exception:
4678
+ try:
4679
+ return copy(step)
4680
+ except Exception:
4681
+ return step
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agno
3
- Version: 2.3.25
3
+ Version: 2.3.26
4
4
  Summary: Agno: a lightweight library for building Multi-Agent Systems
5
5
  Author-email: Ashpreet Bedi <ashpreet@agno.com>
6
6
  Project-URL: homepage, https://agno.com
@@ -6,7 +6,7 @@ agno/media.py,sha256=PisfrNwkx2yVOW8p6LXlV237jI06Y6kGjd7wUMk5170,17121
6
6
  agno/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  agno/table.py,sha256=9hHFnInNsrj0ZKtWjGDP5c6kbmNdtQvDDYT2j2CZJ6o,198
8
8
  agno/agent/__init__.py,sha256=K1umiV73llHpUidDlng0J3T_YY6tbISfsNKw_2S-L1U,1112
9
- agno/agent/agent.py,sha256=gxzUnjD12a5NN61VIH-VRC6VhEOl8xdq3dN9mi7BkFQ,526639
9
+ agno/agent/agent.py,sha256=H9scQ5GJp3cMSvAueypf9DjUWqPcg3cAfvp4PUJC27A,528532
10
10
  agno/agent/remote.py,sha256=EzaT0hhD_2BkrL3ECnxx0oxWnyyc6nPmDbifqvSgkUo,19172
11
11
  agno/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  agno/api/agent.py,sha256=fKlQ62E_C9Rjd7Zus3Gs3R1RG-IhzFV-ICpkb6SLqYc,932
@@ -306,7 +306,7 @@ agno/os/router.py,sha256=Xx3iwxSUSRDEXsQoq5mFuz9MlZIx2YYOkod7RQAaLzw,12348
306
306
  agno/os/schema.py,sha256=GmOy3c52pp1EcVp3QznfHySZwIop1qwB8woX41jeSAE,30527
307
307
  agno/os/scopes.py,sha256=gH3Obo618gHKt5h-N1OkWqB4UPl2LZygd7GW7pKzdAc,15696
308
308
  agno/os/settings.py,sha256=gS9pN1w21wsa5XqDkK-RfQmroAaqoX4KZBfRuhUevkM,1556
309
- agno/os/utils.py,sha256=tOInVknJ9uZk7pD4Z9lhhFXMryPBGKJhdFVtipZfScI,34786
309
+ agno/os/utils.py,sha256=7ym5vG9eRdqppT-ZauFMqGhPVS_ruqeLNhC3BDCqpq0,36860
310
310
  agno/os/interfaces/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
311
311
  agno/os/interfaces/base.py,sha256=N7T-gpSBJeB6xlBn6bhYZNdqG6wXSFj6Uue9TmXhzTE,643
312
312
  agno/os/interfaces/a2a/__init__.py,sha256=Fs7--dx9drvtVS9QjsCCm0P7c-hJ7TzU8gNwKTQsZDA,62
@@ -332,7 +332,7 @@ agno/os/routers/database.py,sha256=PbUq-nQYOROhNiKloKX6qpqKlpqvN6sAnUG31gzgjDk,5
332
332
  agno/os/routers/health.py,sha256=AO3ec6Xi1wXOwUUFJhEcM45488qu6aZRJTwF2GLz6Ak,992
333
333
  agno/os/routers/home.py,sha256=xe8DYJkRgad55qiza0lHt8pUIV5PLSyu2MkybjuPDDE,1708
334
334
  agno/os/routers/agents/__init__.py,sha256=nr1H0Mp7NlWPnvu0ccaHVSPHz-lXg43TRMApkUIEXNc,91
335
- agno/os/routers/agents/router.py,sha256=Y76w6Nc1Wq5bhhUcAbX8AzokZdFauKGwlxtuCrLpENk,25757
335
+ agno/os/routers/agents/router.py,sha256=7TJsIgXHbNp4W3UMAX2g8tgo5O2f39Boau4p8IVZUgo,25833
336
336
  agno/os/routers/agents/schema.py,sha256=dtnFw5e0MFzLmfApHvBwXp0ByN1w10F-swYeZyNkN7c,12776
337
337
  agno/os/routers/evals/__init__.py,sha256=3s0M-Ftg5A3rFyRfTATs-0aNA6wcbj_5tCvtwH9gORQ,87
338
338
  agno/os/routers/evals/evals.py,sha256=9DErwITEV6O1BAr28asB29Yvgy8C5wtzQyz-ys-yJzw,23478
@@ -350,13 +350,13 @@ agno/os/routers/metrics/schemas.py,sha256=1zE3Mfi15SNfF_Hy3r_tLK86E-Jox_qWzSG0oP
350
350
  agno/os/routers/session/__init__.py,sha256=du4LO9aZwiY1t59VcV9M6wiAfftFFlUZc-YXsTGy9LI,97
351
351
  agno/os/routers/session/session.py,sha256=oDAwi6iZTsqkDwt0MehCCUox5IC838CG7TFdgePHQ4c,57764
352
352
  agno/os/routers/teams/__init__.py,sha256=j2aPA09mwwn_vOG8p2uX0sDR-qNbLLRlPlprqM789NY,88
353
- agno/os/routers/teams/router.py,sha256=_PQ2BfsP_zvr7Hczd5GzKi5FwYq6buB26Jur-zOV0mc,24544
353
+ agno/os/routers/teams/router.py,sha256=L2WnVzxGDUsnPvwqnhRUHRqzKRSLJbRBkMR3ABn0DOM,24601
354
354
  agno/os/routers/teams/schema.py,sha256=CaRIiEFXBTF1T5D2RlPCRTIMgyqh2kFlYtbCmkuNYYU,12173
355
355
  agno/os/routers/traces/__init__.py,sha256=v-QMRjlrw1iLkh2UawukKWfZ2R66L-OQmAGR-kqFo8I,93
356
356
  agno/os/routers/traces/schemas.py,sha256=_O7tfF3aAPR-R1Q9noakWxjbI20UxkemVh7P2Dkw71I,20034
357
357
  agno/os/routers/traces/traces.py,sha256=6fXnHacKJQULAG_Sh-Ci2sFGn1XMsXufRJM4GVxywOU,24413
358
358
  agno/os/routers/workflows/__init__.py,sha256=1VnCzNTwxT9Z9EHskfqtrl1zhXGPPKuR4TktYdKd1RI,146
359
- agno/os/routers/workflows/router.py,sha256=IWkpaw7QhuumJUDUv82BV4yFRtppDsikPfY7c5SpKS8,31080
359
+ agno/os/routers/workflows/router.py,sha256=gyk55lZnXER2WoqfLChlYG1JX4T1bP3WtBRYYYTUNFM,31175
360
360
  agno/os/routers/workflows/schema.py,sha256=ovFO5RNL4UPXDNHZuQzOHZJsWkiBtoLX5D-Plp__AQ4,5739
361
361
  agno/reasoning/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
362
362
  agno/reasoning/anthropic.py,sha256=WzUrGLLdbH31T6uBafYKt7W0wXMYHbXLxYRiVs2ge5U,6334
@@ -401,7 +401,7 @@ agno/skills/loaders/base.py,sha256=Syt6lIOe-m_Kz68ndwuVed3wZFAPvYDDnJkhypazYJ8,7
401
401
  agno/skills/loaders/local.py,sha256=7budE7d-JG86XyqnRwo495yiYWDjj16yDV67aiSacOQ,7478
402
402
  agno/team/__init__.py,sha256=7oYaEB1iTGbmajpgSd3tjCQAQo6t0PIE8ZT1m5MKTm0,905
403
403
  agno/team/remote.py,sha256=BgUEQsTFA4tehDas1YO7lwQCj3cLJrxfoSFVOYm_1po,16979
404
- agno/team/team.py,sha256=w6kBmTMjsudqigrw8_lxC-RazH-YFUPhO3y_wfCqFNs,434821
404
+ agno/team/team.py,sha256=3DMRu7UjaRZubGCkDErQZbFYN5XV2XTzP--1-bZW01E,440036
405
405
  agno/tools/__init__.py,sha256=jNll2sELhPPbqm5nPeT4_uyzRO2_KRTW-8Or60kioS0,210
406
406
  agno/tools/agentql.py,sha256=S82Z9aTNr-E5wnA4fbFs76COljJtiQIjf2grjz3CkHU,4104
407
407
  agno/tools/airflow.py,sha256=uf2rOzZpSU64l_qRJ5Raku-R3Gky-uewmYkh6W0-oxg,2610
@@ -649,9 +649,9 @@ agno/workflow/router.py,sha256=s0fZ8CHu0Q4Ngygs7N4j-Lq1NHBqBmGExckBURWwZ4I,32340
649
649
  agno/workflow/step.py,sha256=voTmWWihLKDAMeLgYoie96KLCiVpxPFrZoFHEMhA6QM,75046
650
650
  agno/workflow/steps.py,sha256=1UySQjqwHlFHVhUL9r2hqy1K3esISjh2bvtClQk5PiI,27482
651
651
  agno/workflow/types.py,sha256=t4304WCKB19QFdV3ixXZICcU8wtBza4EBCIz5Ve6MSQ,18035
652
- agno/workflow/workflow.py,sha256=Z_-3ZLux2VxyqK2kgr_YcvCwg_YtSWVjHszXOkVoMy8,196292
653
- agno-2.3.25.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
654
- agno-2.3.25.dist-info/METADATA,sha256=hSHwGEmxkTL7Sw44Lneiq3M-VX4MyibdZI_xKn9cD7o,24218
655
- agno-2.3.25.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
656
- agno-2.3.25.dist-info/top_level.txt,sha256=MKyeuVesTyOKIXUhc-d_tPa2Hrh0oTA4LM0izowpx70,5
657
- agno-2.3.25.dist-info/RECORD,,
652
+ agno/workflow/workflow.py,sha256=rcpABeUkJ6svT9esj2WxN-tGKobMSDbuLnzBz6s-Xp4,204812
653
+ agno-2.3.26.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
654
+ agno-2.3.26.dist-info/METADATA,sha256=EXxNtbLgnNxV7iDN5t2U5hOtmtRU-NiuWfSoE7vf-FE,24218
655
+ agno-2.3.26.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
656
+ agno-2.3.26.dist-info/top_level.txt,sha256=MKyeuVesTyOKIXUhc-d_tPa2Hrh0oTA4LM0izowpx70,5
657
+ agno-2.3.26.dist-info/RECORD,,
File without changes