agno 2.3.26__py3-none-any.whl → 2.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. agno/agent/__init__.py +4 -0
  2. agno/agent/agent.py +1368 -541
  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 +1242 -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 +1186 -13
  11. agno/db/utils.py +37 -1
  12. agno/integrations/discord/client.py +12 -1
  13. agno/knowledge/__init__.py +4 -0
  14. agno/knowledge/chunking/code.py +1 -1
  15. agno/knowledge/chunking/semantic.py +1 -1
  16. agno/knowledge/chunking/strategy.py +4 -0
  17. agno/knowledge/filesystem.py +412 -0
  18. agno/knowledge/knowledge.py +3722 -2182
  19. agno/knowledge/protocol.py +134 -0
  20. agno/knowledge/reader/arxiv_reader.py +2 -2
  21. agno/knowledge/reader/base.py +9 -7
  22. agno/knowledge/reader/csv_reader.py +236 -13
  23. agno/knowledge/reader/docx_reader.py +2 -2
  24. agno/knowledge/reader/field_labeled_csv_reader.py +169 -5
  25. agno/knowledge/reader/firecrawl_reader.py +2 -2
  26. agno/knowledge/reader/json_reader.py +2 -2
  27. agno/knowledge/reader/markdown_reader.py +2 -2
  28. agno/knowledge/reader/pdf_reader.py +5 -4
  29. agno/knowledge/reader/pptx_reader.py +2 -2
  30. agno/knowledge/reader/reader_factory.py +118 -1
  31. agno/knowledge/reader/s3_reader.py +2 -2
  32. agno/knowledge/reader/tavily_reader.py +2 -2
  33. agno/knowledge/reader/text_reader.py +2 -2
  34. agno/knowledge/reader/web_search_reader.py +2 -2
  35. agno/knowledge/reader/website_reader.py +5 -3
  36. agno/knowledge/reader/wikipedia_reader.py +2 -2
  37. agno/knowledge/reader/youtube_reader.py +2 -2
  38. agno/knowledge/remote_content/__init__.py +29 -0
  39. agno/knowledge/remote_content/config.py +204 -0
  40. agno/knowledge/remote_content/remote_content.py +74 -17
  41. agno/knowledge/utils.py +37 -29
  42. agno/learn/__init__.py +6 -0
  43. agno/learn/machine.py +35 -0
  44. agno/learn/schemas.py +82 -11
  45. agno/learn/stores/__init__.py +3 -0
  46. agno/learn/stores/decision_log.py +1156 -0
  47. agno/learn/stores/learned_knowledge.py +6 -6
  48. agno/models/anthropic/claude.py +24 -0
  49. agno/models/aws/bedrock.py +20 -0
  50. agno/models/base.py +60 -6
  51. agno/models/cerebras/cerebras.py +34 -2
  52. agno/models/cohere/chat.py +25 -0
  53. agno/models/google/gemini.py +50 -5
  54. agno/models/litellm/chat.py +38 -0
  55. agno/models/n1n/__init__.py +3 -0
  56. agno/models/n1n/n1n.py +57 -0
  57. agno/models/openai/chat.py +25 -1
  58. agno/models/openrouter/openrouter.py +46 -0
  59. agno/models/perplexity/perplexity.py +2 -0
  60. agno/models/response.py +16 -0
  61. agno/os/app.py +83 -44
  62. agno/os/interfaces/slack/router.py +10 -1
  63. agno/os/interfaces/whatsapp/router.py +6 -0
  64. agno/os/middleware/__init__.py +2 -0
  65. agno/os/middleware/trailing_slash.py +27 -0
  66. agno/os/router.py +1 -0
  67. agno/os/routers/agents/router.py +29 -16
  68. agno/os/routers/agents/schema.py +6 -4
  69. agno/os/routers/components/__init__.py +3 -0
  70. agno/os/routers/components/components.py +475 -0
  71. agno/os/routers/evals/schemas.py +4 -3
  72. agno/os/routers/health.py +3 -3
  73. agno/os/routers/knowledge/knowledge.py +128 -3
  74. agno/os/routers/knowledge/schemas.py +12 -0
  75. agno/os/routers/memory/schemas.py +4 -2
  76. agno/os/routers/metrics/metrics.py +9 -11
  77. agno/os/routers/metrics/schemas.py +10 -6
  78. agno/os/routers/registry/__init__.py +3 -0
  79. agno/os/routers/registry/registry.py +337 -0
  80. agno/os/routers/teams/router.py +20 -8
  81. agno/os/routers/teams/schema.py +6 -4
  82. agno/os/routers/traces/traces.py +5 -5
  83. agno/os/routers/workflows/router.py +38 -11
  84. agno/os/routers/workflows/schema.py +1 -1
  85. agno/os/schema.py +92 -26
  86. agno/os/utils.py +84 -19
  87. agno/reasoning/anthropic.py +2 -2
  88. agno/reasoning/azure_ai_foundry.py +2 -2
  89. agno/reasoning/deepseek.py +2 -2
  90. agno/reasoning/default.py +6 -7
  91. agno/reasoning/gemini.py +2 -2
  92. agno/reasoning/helpers.py +6 -7
  93. agno/reasoning/manager.py +4 -10
  94. agno/reasoning/ollama.py +2 -2
  95. agno/reasoning/openai.py +2 -2
  96. agno/reasoning/vertexai.py +2 -2
  97. agno/registry/__init__.py +3 -0
  98. agno/registry/registry.py +68 -0
  99. agno/run/agent.py +59 -0
  100. agno/run/base.py +7 -0
  101. agno/run/team.py +57 -0
  102. agno/skills/agent_skills.py +10 -3
  103. agno/team/__init__.py +3 -1
  104. agno/team/team.py +1165 -330
  105. agno/tools/duckduckgo.py +25 -71
  106. agno/tools/exa.py +0 -21
  107. agno/tools/function.py +35 -83
  108. agno/tools/knowledge.py +9 -4
  109. agno/tools/mem0.py +11 -10
  110. agno/tools/memory.py +47 -46
  111. agno/tools/parallel.py +0 -7
  112. agno/tools/reasoning.py +30 -23
  113. agno/tools/tavily.py +4 -1
  114. agno/tools/websearch.py +93 -0
  115. agno/tools/website.py +1 -1
  116. agno/tools/wikipedia.py +1 -1
  117. agno/tools/workflow.py +48 -47
  118. agno/utils/agent.py +42 -5
  119. agno/utils/events.py +160 -2
  120. agno/utils/print_response/agent.py +0 -31
  121. agno/utils/print_response/team.py +0 -2
  122. agno/utils/print_response/workflow.py +0 -2
  123. agno/utils/team.py +61 -11
  124. agno/vectordb/lancedb/lance_db.py +4 -1
  125. agno/vectordb/mongodb/mongodb.py +1 -1
  126. agno/vectordb/pgvector/pgvector.py +3 -3
  127. agno/vectordb/qdrant/qdrant.py +4 -4
  128. agno/workflow/__init__.py +3 -1
  129. agno/workflow/condition.py +0 -21
  130. agno/workflow/loop.py +0 -21
  131. agno/workflow/parallel.py +0 -21
  132. agno/workflow/router.py +0 -21
  133. agno/workflow/step.py +117 -24
  134. agno/workflow/steps.py +0 -21
  135. agno/workflow/workflow.py +427 -63
  136. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/METADATA +49 -76
  137. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/RECORD +140 -126
  138. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/WHEEL +1 -1
  139. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/licenses/LICENSE +0 -0
  140. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/top_level.txt +0 -0
agno/models/response.py CHANGED
@@ -16,6 +16,10 @@ class ModelResponseEvent(str, Enum):
16
16
  tool_call_started = "ToolCallStarted"
17
17
  tool_call_completed = "ToolCallCompleted"
18
18
  assistant_response = "AssistantResponse"
19
+ compression_started = "CompressionStarted"
20
+ compression_completed = "CompressionCompleted"
21
+ model_request_started = "ModelRequestStarted"
22
+ model_request_completed = "ModelRequestCompleted"
19
23
 
20
24
 
21
25
  @dataclass
@@ -124,6 +128,18 @@ class ModelResponse:
124
128
 
125
129
  updated_session_state: Optional[Dict[str, Any]] = None
126
130
 
131
+ # Compression stats
132
+ compression_stats: Optional[Dict[str, Any]] = None
133
+
134
+ # Model request metrics (for model_request_completed events)
135
+ input_tokens: Optional[int] = None
136
+ output_tokens: Optional[int] = None
137
+ total_tokens: Optional[int] = None
138
+ time_to_first_token: Optional[float] = None
139
+ reasoning_tokens: Optional[int] = None
140
+ cache_read_tokens: Optional[int] = None
141
+ cache_write_tokens: Optional[int] = None
142
+
127
143
  def to_dict(self) -> Dict[str, Any]:
128
144
  """Serialize ModelResponse to dictionary for caching."""
129
145
  _dict = asdict(self)
agno/os/app.py CHANGED
@@ -36,6 +36,7 @@ from agno.os.config import (
36
36
  from agno.os.interfaces.base import BaseInterface
37
37
  from agno.os.router import get_base_router, get_websocket_router
38
38
  from agno.os.routers.agents import get_agent_router
39
+ from agno.os.routers.components import get_components_router
39
40
  from agno.os.routers.database import get_database_router
40
41
  from agno.os.routers.evals import get_eval_router
41
42
  from agno.os.routers.health import get_health_router
@@ -43,6 +44,7 @@ from agno.os.routers.home import get_home_router
43
44
  from agno.os.routers.knowledge import get_knowledge_router
44
45
  from agno.os.routers.memory import get_memory_router
45
46
  from agno.os.routers.metrics import get_metrics_router
47
+ from agno.os.routers.registry import get_registry_router
46
48
  from agno.os.routers.session import get_session_router
47
49
  from agno.os.routers.teams import get_team_router
48
50
  from agno.os.routers.traces import get_traces_router
@@ -57,6 +59,7 @@ from agno.os.utils import (
57
59
  setup_tracing_for_os,
58
60
  update_cors_middleware,
59
61
  )
62
+ from agno.registry import Registry
60
63
  from agno.remote.base import RemoteDb, RemoteKnowledge
61
64
  from agno.team import RemoteTeam, Team
62
65
  from agno.utils.log import log_debug, log_error, log_info, log_warning
@@ -129,6 +132,7 @@ class AgentOS:
129
132
  name: Optional[str] = None,
130
133
  description: Optional[str] = None,
131
134
  version: Optional[str] = None,
135
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
132
136
  agents: Optional[List[Union[Agent, RemoteAgent]]] = None,
133
137
  teams: Optional[List[Union[Team, RemoteTeam]]] = None,
134
138
  workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = None,
@@ -145,10 +149,10 @@ class AgentOS:
145
149
  base_app: Optional[FastAPI] = None,
146
150
  on_route_conflict: Literal["preserve_agentos", "preserve_base_app", "error"] = "preserve_agentos",
147
151
  tracing: bool = False,
148
- tracing_db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
149
152
  auto_provision_dbs: bool = True,
150
153
  run_hooks_in_background: bool = False,
151
154
  telemetry: bool = True,
155
+ registry: Optional[Registry] = None,
152
156
  ):
153
157
  """Initialize AgentOS.
154
158
 
@@ -157,6 +161,7 @@ class AgentOS:
157
161
  name: Name of the AgentOS instance
158
162
  description: Description of the AgentOS instance
159
163
  version: Version of the AgentOS instance
164
+ db: Default database for the AgentOS instance. Agents, teams and workflows with no db will use this one.
160
165
  agents: List of agents to include in the OS
161
166
  teams: List of teams to include in the OS
162
167
  workflows: List of workflows to include in the OS
@@ -174,14 +179,13 @@ class AgentOS:
174
179
  authorization_config: Configuration for the authorization middleware
175
180
  cors_allowed_origins: List of allowed CORS origins (will be merged with default Agno domains)
176
181
  tracing: If True, enables OpenTelemetry tracing for all agents and teams in the OS
177
- tracing_db: Dedicated database for storing and reading traces. Recommended for multi-db setups.
178
- If not provided and tracing=True, the first available db from agents/teams/workflows is used.
179
182
  run_hooks_in_background: If True, run agent/team pre/post hooks as FastAPI background tasks (non-blocking)
180
183
  telemetry: Whether to enable telemetry
184
+ registry: Optional registry to use for the AgentOS
181
185
 
182
186
  """
183
- if not agents and not workflows and not teams and not knowledge:
184
- raise ValueError("Either agents, teams, workflows or knowledge bases must be provided.")
187
+ if not agents and not workflows and not teams and not knowledge and not db:
188
+ raise ValueError("Either agents, teams, workflows, knowledge bases or a database must be provided.")
185
189
 
186
190
  self.config = load_yaml_config(config) if isinstance(config, str) else config
187
191
 
@@ -214,14 +218,16 @@ class AgentOS:
214
218
 
215
219
  self.version = version
216
220
  self.description = description
221
+ self.db = db
217
222
 
218
223
  self.telemetry = telemetry
219
224
  self.tracing = tracing
220
- self.tracing_db = tracing_db
221
225
 
222
226
  self.enable_mcp_server = enable_mcp_server
223
227
  self.lifespan = lifespan
224
228
 
229
+ self.registry = registry
230
+
225
231
  # RBAC
226
232
  self.authorization = authorization
227
233
  self.authorization_config = authorization_config
@@ -309,6 +315,12 @@ class AgentOS:
309
315
  get_traces_router(dbs=self.dbs),
310
316
  get_database_router(self, settings=self.settings),
311
317
  ]
318
+ # Add component and registry routers only if a sync db (BaseDb) is available
319
+ # Component routes require sync database operations
320
+ if self.db is not None and isinstance(self.db, BaseDb):
321
+ updated_routers.append(get_components_router(os_db=self.db, registry=self.registry))
322
+ if self.registry is not None:
323
+ updated_routers.append(get_registry_router(registry=self.registry))
312
324
 
313
325
  # Clear all previously existing routes
314
326
  app.router.routes = [
@@ -338,8 +350,8 @@ class AgentOS:
338
350
 
339
351
  self._add_router(app, get_health_router(health_endpoint="/health"))
340
352
  self._add_router(app, get_base_router(self, settings=self.settings))
341
- self._add_router(app, get_agent_router(self, settings=self.settings))
342
- self._add_router(app, get_team_router(self, settings=self.settings))
353
+ self._add_router(app, get_agent_router(self, settings=self.settings, registry=self.registry))
354
+ self._add_router(app, get_team_router(self, settings=self.settings, registry=self.registry))
343
355
  self._add_router(app, get_workflow_router(self, settings=self.settings))
344
356
  self._add_router(app, get_websocket_router(self, settings=self.settings))
345
357
 
@@ -418,6 +430,9 @@ class AgentOS:
418
430
  for agent in self.agents:
419
431
  if isinstance(agent, RemoteAgent):
420
432
  continue
433
+ # Set the default db to agents without their own
434
+ if self.db is not None and agent.db is None:
435
+ agent.db = self.db
421
436
  # Track all MCP tools to later handle their connection
422
437
  if agent.tools:
423
438
  for tool in agent.tools:
@@ -444,6 +459,11 @@ class AgentOS:
444
459
  for team in self.teams:
445
460
  if isinstance(team, RemoteTeam):
446
461
  continue
462
+
463
+ # Set the default db to teams without their own
464
+ if self.db is not None and team.db is None:
465
+ team.db = self.db
466
+
447
467
  # Track all MCP tools recursively
448
468
  collect_mcp_tools_from_team(team, self.mcp_tools)
449
469
 
@@ -467,31 +487,34 @@ class AgentOS:
467
487
  if not self.workflows:
468
488
  return
469
489
 
470
- if self.workflows:
471
- for workflow in self.workflows:
472
- if isinstance(workflow, RemoteWorkflow):
473
- continue
474
- # Track MCP tools recursively in workflow members
475
- collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
490
+ for workflow in self.workflows:
491
+ if isinstance(workflow, RemoteWorkflow):
492
+ continue
493
+ # Set the default db to workflows without their own
494
+ if self.db is not None and workflow.db is None:
495
+ workflow.db = self.db
476
496
 
477
- if not workflow.id:
478
- workflow.id = generate_id_from_name(workflow.name)
497
+ # Track MCP tools recursively in workflow members
498
+ collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
479
499
 
480
- # Required for the built-in routes to work
481
- workflow.store_events = True
500
+ if not workflow.id:
501
+ workflow.id = generate_id_from_name(workflow.name)
482
502
 
483
- # Propagate run_hooks_in_background setting to workflow and all its step agents/teams
484
- workflow.propagate_run_hooks_in_background(self.run_hooks_in_background)
503
+ # Required for the built-in routes to work
504
+ workflow.store_events = True
505
+
506
+ # Propagate run_hooks_in_background setting to workflow and all its step agents/teams
507
+ workflow.propagate_run_hooks_in_background(self.run_hooks_in_background)
485
508
 
486
509
  def _setup_tracing(self) -> None:
487
510
  """Set up OpenTelemetry tracing for this AgentOS.
488
511
 
489
- Uses tracing_db if provided, otherwise falls back to the first available
512
+ Uses the AgentOS db if provided, otherwise falls back to the first available
490
513
  database from agents/teams/workflows.
491
514
  """
492
- # Use tracing_db if explicitly provided
493
- if self.tracing_db is not None:
494
- setup_tracing_for_os(db=self.tracing_db)
515
+ # Use AgentOS db if explicitly provided
516
+ if self.db is not None:
517
+ setup_tracing_for_os(db=self.db)
495
518
  return
496
519
 
497
520
  # Fall back to finding the first available database
@@ -517,7 +540,7 @@ class AgentOS:
517
540
  if db is None:
518
541
  log_warning(
519
542
  "tracing=True but no database found. "
520
- "Provide 'tracing_db' parameter or 'db' parameter to at least one agent/team/workflow."
543
+ "Provide 'db' parameter to AgentOS or to at least one agent/team/workflow."
521
544
  )
522
545
  return
523
546
 
@@ -605,6 +628,12 @@ class AgentOS:
605
628
  get_traces_router(dbs=self.dbs),
606
629
  get_database_router(self, settings=self.settings),
607
630
  ]
631
+ # Add component and registry routers only if a sync db (BaseDb) is available
632
+ # Component routes require sync database operations
633
+ if self.db is not None and isinstance(self.db, BaseDb):
634
+ routers.append(get_components_router(os_db=self.db, registry=self.registry))
635
+ if self.registry is not None:
636
+ routers.append(get_registry_router(registry=self.registry))
608
637
 
609
638
  for router in routers:
610
639
  self._add_router(fastapi_app, router)
@@ -676,6 +705,11 @@ class AgentOS:
676
705
 
677
706
  self._add_jwt_middleware(fastapi_app)
678
707
 
708
+ # Add trailing slash normalization middleware
709
+ from agno.os.middleware.trailing_slash import TrailingSlashMiddleware
710
+
711
+ fastapi_app.add_middleware(TrailingSlashMiddleware)
712
+
679
713
  return fastapi_app
680
714
 
681
715
  def _add_jwt_middleware(self, fastapi_app: FastAPI) -> None:
@@ -807,14 +841,16 @@ class AgentOS:
807
841
  for agent in self.agents or []:
808
842
  if agent.db:
809
843
  self._register_db_with_validation(dbs, agent.db)
810
- if agent.knowledge and agent.knowledge.contents_db:
811
- self._register_db_with_validation(knowledge_dbs, agent.knowledge.contents_db)
844
+ agent_contents_db = getattr(agent.knowledge, "contents_db", None) if agent.knowledge else None
845
+ if agent_contents_db:
846
+ self._register_db_with_validation(knowledge_dbs, agent_contents_db)
812
847
 
813
848
  for team in self.teams or []:
814
849
  if team.db:
815
850
  self._register_db_with_validation(dbs, team.db)
816
- if team.knowledge and team.knowledge.contents_db:
817
- self._register_db_with_validation(knowledge_dbs, team.knowledge.contents_db)
851
+ team_contents_db = getattr(team.knowledge, "contents_db", None) if team.knowledge else None
852
+ if team_contents_db:
853
+ self._register_db_with_validation(knowledge_dbs, team_contents_db)
818
854
 
819
855
  for workflow in self.workflows or []:
820
856
  if workflow.db:
@@ -830,9 +866,9 @@ class AgentOS:
830
866
  elif interface.team and interface.team.db:
831
867
  self._register_db_with_validation(dbs, interface.team.db)
832
868
 
833
- # Register tracing_db if provided (for traces reading)
834
- if self.tracing_db is not None:
835
- self._register_db_with_validation(dbs, self.tracing_db)
869
+ # Register AgentOS db if provided
870
+ if self.db is not None:
871
+ self._register_db_with_validation(dbs, self.db)
836
872
 
837
873
  self.dbs = dbs
838
874
  self.knowledge_dbs = knowledge_dbs
@@ -939,18 +975,21 @@ class AgentOS:
939
975
 
940
976
  def _auto_discover_knowledge_instances(self) -> None:
941
977
  """Auto-discover the knowledge instances used by all contextual agents, teams and workflows."""
942
- seen_ids = set()
978
+ seen_ids: set[str] = set()
943
979
  knowledge_instances: List[Union[Knowledge, RemoteKnowledge]] = []
944
980
 
945
- def _add_knowledge_if_not_duplicate(knowledge: Union["Knowledge", RemoteKnowledge]) -> None:
981
+ def _add_knowledge_if_not_duplicate(knowledge: Any) -> None:
946
982
  """Add knowledge instance if it's not already in the list (by object identity or db_id)."""
947
- # Use database ID if available, otherwise use object ID as fallback
948
- if not knowledge.contents_db:
983
+ # Only handle Knowledge and RemoteKnowledge instances that have contents_db
984
+ contents_db = getattr(knowledge, "contents_db", None)
985
+ if not contents_db:
949
986
  return
950
- if knowledge.contents_db.id in seen_ids:
987
+ if contents_db.id in seen_ids:
951
988
  return
952
- seen_ids.add(knowledge.contents_db.id)
953
- knowledge_instances.append(knowledge)
989
+ seen_ids.add(contents_db.id)
990
+ # Only append if it's a Knowledge or RemoteKnowledge instance
991
+ if isinstance(knowledge, (Knowledge, RemoteKnowledge)):
992
+ knowledge_instances.append(knowledge)
954
993
 
955
994
  for agent in self.agents or []:
956
995
  if agent.knowledge:
@@ -1080,13 +1119,13 @@ class AgentOS:
1080
1119
 
1081
1120
  dbs_with_specific_config = [db.db_id for db in traces_config.dbs]
1082
1121
 
1083
- # If tracing_db is explicitly set, only use that database for traces
1084
- if self.tracing_db is not None:
1085
- if self.tracing_db.id not in dbs_with_specific_config:
1122
+ # If AgentOS db is explicitly set, only use that database for traces
1123
+ if self.db is not None:
1124
+ if self.db.id not in dbs_with_specific_config:
1086
1125
  traces_config.dbs.append(
1087
1126
  DatabaseConfig(
1088
- db_id=self.tracing_db.id,
1089
- domain_config=TracesDomainConfig(display_name=self.tracing_db.id),
1127
+ db_id=self.db.id,
1128
+ domain_config=TracesDomainConfig(display_name=self.db.id),
1090
1129
  )
1091
1130
  )
1092
1131
  else:
@@ -7,7 +7,7 @@ from agno.agent import Agent, RemoteAgent
7
7
  from agno.os.interfaces.slack.security import verify_slack_signature
8
8
  from agno.team import RemoteTeam, Team
9
9
  from agno.tools.slack import SlackTools
10
- from agno.utils.log import log_info
10
+ from agno.utils.log import log_error, log_info
11
11
  from agno.workflow import RemoteWorkflow, Workflow
12
12
 
13
13
 
@@ -112,6 +112,15 @@ def attach_routes(
112
112
  response = await workflow.arun(message_text, user_id=user, session_id=session_id) # type: ignore
113
113
 
114
114
  if response:
115
+ if response.status == "ERROR":
116
+ log_error(f"Error processing message: {response.content}")
117
+ _send_slack_message(
118
+ channel=channel_id,
119
+ message="Sorry, there was an error processing your message. Please try again later.",
120
+ thread_ts=ts,
121
+ )
122
+ return
123
+
115
124
  if hasattr(response, "reasoning_content") and response.reasoning_content:
116
125
  _send_slack_message(
117
126
  channel=channel_id,
@@ -162,6 +162,12 @@ def attach_routes(
162
162
  videos=[Video(content=await get_media_async(message_video))] if message_video else None,
163
163
  audio=[Audio(content=await get_media_async(message_audio))] if message_audio else None,
164
164
  )
165
+ if response.status == "ERROR":
166
+ await _send_whatsapp_message(
167
+ phone_number, "Sorry, there was an error processing your message. Please try again later."
168
+ )
169
+ log_error(response.content)
170
+ return
165
171
 
166
172
  if response.reasoning_content:
167
173
  await _send_whatsapp_message(phone_number, f"Reasoning: \n{response.reasoning_content}", italics=True)
@@ -2,8 +2,10 @@ from agno.os.middleware.jwt import (
2
2
  JWTMiddleware,
3
3
  TokenSource,
4
4
  )
5
+ from agno.os.middleware.trailing_slash import TrailingSlashMiddleware
5
6
 
6
7
  __all__ = [
7
8
  "JWTMiddleware",
8
9
  "TokenSource",
10
+ "TrailingSlashMiddleware",
9
11
  ]
@@ -0,0 +1,27 @@
1
+ from starlette.middleware.base import BaseHTTPMiddleware
2
+ from starlette.requests import Request
3
+ from starlette.responses import Response
4
+
5
+
6
+ class TrailingSlashMiddleware(BaseHTTPMiddleware):
7
+ """
8
+ Middleware that strips trailing slashes from request paths.
9
+
10
+ This ensures that both /agents and /agents/ are handled identically
11
+ without requiring a redirect. Updates both 'path' and 'raw_path'
12
+ """
13
+
14
+ async def dispatch(self, request: Request, call_next) -> Response:
15
+ # Get the path from the request scope
16
+ path = request.scope.get("path", "")
17
+
18
+ # Strip trailing slash if path is not root "/"
19
+ if path != "/" and path.endswith("/"):
20
+ normalized_path = path.rstrip("/")
21
+ if normalized_path: # Ensure we don't end up with empty path
22
+ # Modify the scope to remove trailing slash
23
+ request.scope["path"] = normalized_path
24
+ # Update raw_path for ASGI spec compliance
25
+ request.scope["raw_path"] = normalized_path.encode("utf-8")
26
+
27
+ return await call_next(request)
agno/os/router.py CHANGED
@@ -165,6 +165,7 @@ def get_base_router(
165
165
  os_id=os.id or "Unnamed OS",
166
166
  description=os.description,
167
167
  available_models=os.config.available_models if os.config else [],
168
+ os_database=os.db.id if os.db else None,
168
169
  databases=list({db.id for db_id, dbs in os.dbs.items() for db in dbs}),
169
170
  chat=os.config.chat if os.config else None,
170
171
  session=os._get_session_config(),
@@ -16,6 +16,7 @@ from fastapi.responses import JSONResponse, StreamingResponse
16
16
 
17
17
  from agno.agent.agent import Agent
18
18
  from agno.agent.remote import RemoteAgent
19
+ from agno.db.base import BaseDb
19
20
  from agno.exceptions import InputCheckError, OutputCheckError
20
21
  from agno.media import Audio, Image, Video
21
22
  from agno.media import File as FileMedia
@@ -38,6 +39,7 @@ from agno.os.utils import (
38
39
  process_image,
39
40
  process_video,
40
41
  )
42
+ from agno.registry import Registry
41
43
  from agno.run.agent import RunErrorEvent, RunOutput
42
44
  from agno.utils.log import log_debug, log_error, log_warning
43
45
 
@@ -156,6 +158,7 @@ async def agent_continue_response_streamer(
156
158
  def get_agent_router(
157
159
  os: "AgentOS",
158
160
  settings: AgnoAPISettings = AgnoAPISettings(),
161
+ registry: Optional[Registry] = None,
159
162
  ) -> APIRouter:
160
163
  """
161
164
  Create the agent router with comprehensive OpenAPI documentation.
@@ -216,6 +219,7 @@ def get_agent_router(
216
219
  session_id: Optional[str] = Form(None),
217
220
  user_id: Optional[str] = Form(None),
218
221
  files: Optional[List[UploadFile]] = File(None),
222
+ version: Optional[str] = Form(None),
219
223
  ):
220
224
  kwargs = await get_request_kwargs(request, create_agent_run)
221
225
 
@@ -243,7 +247,9 @@ def get_agent_router(
243
247
  log_warning("Metadata parameter passed in both request state and kwargs, using request state")
244
248
  kwargs["metadata"] = metadata
245
249
 
246
- agent = get_agent_by_id(agent_id, os.agents, create_fresh=True)
250
+ agent = get_agent_by_id(
251
+ agent_id, os.agents, os.db, registry, version=int(version) if version else None, create_fresh=True
252
+ )
247
253
  if agent is None:
248
254
  raise HTTPException(status_code=404, detail="Agent not found")
249
255
 
@@ -405,7 +411,7 @@ def get_agent_router(
405
411
  agent_id: str,
406
412
  run_id: str,
407
413
  ):
408
- agent = get_agent_by_id(agent_id, os.agents, create_fresh=True)
414
+ agent = get_agent_by_id(agent_id=agent_id, agents=os.agents, db=os.db, registry=os.registry, create_fresh=True)
409
415
  if agent is None:
410
416
  raise HTTPException(status_code=404, detail="Agent not found")
411
417
 
@@ -464,7 +470,7 @@ def get_agent_router(
464
470
  except json.JSONDecodeError:
465
471
  raise HTTPException(status_code=400, detail="Invalid JSON in tools field")
466
472
 
467
- agent = get_agent_by_id(agent_id, os.agents, create_fresh=True)
473
+ agent = get_agent_by_id(agent_id=agent_id, agents=os.agents, db=os.db, registry=os.registry, create_fresh=True)
468
474
  if agent is None:
469
475
  raise HTTPException(status_code=404, detail="Agent not found")
470
476
 
@@ -563,9 +569,6 @@ def get_agent_router(
563
569
  )
564
570
  async def get_agents(request: Request) -> List[AgentResponse]:
565
571
  """Return the list of all Agents present in the contextual OS"""
566
- if os.agents is None:
567
- return []
568
-
569
572
  # Filter agents based on user's scopes (only if authorization is enabled)
570
573
  if getattr(request.state, "authorization_enabled", False):
571
574
  from agno.os.auth import filter_resources_by_access, get_accessible_resources
@@ -576,17 +579,27 @@ def get_agent_router(
576
579
  raise HTTPException(status_code=403, detail="Insufficient permissions")
577
580
 
578
581
  # Limit results based on the user's access/scopes
579
- accessible_agents = filter_resources_by_access(request, os.agents, "agents")
582
+ accessible_agents = filter_resources_by_access(request, os.agents or [], "agents")
580
583
  else:
581
- accessible_agents = os.agents
584
+ accessible_agents = os.agents or []
585
+
586
+ agents: List[AgentResponse] = []
587
+ if accessible_agents:
588
+ for agent in accessible_agents:
589
+ if isinstance(agent, RemoteAgent):
590
+ agents.append(await agent.get_agent_config())
591
+ else:
592
+ agent_response = await AgentResponse.from_agent(agent=agent)
593
+ agents.append(agent_response)
594
+
595
+ if os.db and isinstance(os.db, BaseDb):
596
+ from agno.agent.agent import get_agents
582
597
 
583
- agents = []
584
- for agent in accessible_agents:
585
- if isinstance(agent, RemoteAgent):
586
- agents.append(await agent.get_agent_config())
587
- else:
588
- agent_response = await AgentResponse.from_agent(agent=agent)
589
- agents.append(agent_response)
598
+ db_agents = get_agents(db=os.db, registry=registry)
599
+ if db_agents:
600
+ for db_agent in db_agents:
601
+ agent_response = await AgentResponse.from_agent(agent=db_agent)
602
+ agents.append(agent_response)
590
603
 
591
604
  return agents
592
605
 
@@ -630,7 +643,7 @@ def get_agent_router(
630
643
  dependencies=[Depends(require_resource_access("agents", "read", "agent_id"))],
631
644
  )
632
645
  async def get_agent(agent_id: str, request: Request) -> AgentResponse:
633
- agent = get_agent_by_id(agent_id, os.agents, create_fresh=True)
646
+ agent = get_agent_by_id(agent_id=agent_id, agents=os.agents, db=os.db, registry=os.registry, create_fresh=True)
634
647
  if agent is None:
635
648
  raise HTTPException(status_code=404, detail="Agent not found")
636
649
 
@@ -65,7 +65,7 @@ class AgentResponse(BaseModel):
65
65
  "enable_agentic_knowledge_filters": False,
66
66
  # Memory defaults
67
67
  "enable_agentic_memory": False,
68
- "enable_user_memories": False,
68
+ "update_memory_on_run": False,
69
69
  # Reasoning defaults
70
70
  "reasoning": False,
71
71
  "reasoning_min_steps": 1,
@@ -151,8 +151,9 @@ class AgentResponse(BaseModel):
151
151
  "cache_session": agent.cache_session,
152
152
  }
153
153
 
154
+ contents_db = getattr(agent.knowledge, "contents_db", None) if agent.knowledge else None
154
155
  knowledge_info = {
155
- "db_id": agent.knowledge.contents_db.id if agent.knowledge and agent.knowledge.contents_db else None,
156
+ "db_id": contents_db.id if contents_db else None,
156
157
  "knowledge_table": knowledge_table,
157
158
  "enable_agentic_knowledge_filters": agent.enable_agentic_knowledge_filters,
158
159
  "knowledge_filters": agent.knowledge_filters,
@@ -163,9 +164,10 @@ class AgentResponse(BaseModel):
163
164
  if agent.memory_manager is not None:
164
165
  memory_info = {
165
166
  "enable_agentic_memory": agent.enable_agentic_memory,
166
- "enable_user_memories": agent.enable_user_memories,
167
+ "update_memory_on_run": agent.update_memory_on_run,
168
+ "enable_user_memories": agent.enable_user_memories, # Soon to be deprecated. Use update_memory_on_run
167
169
  "metadata": agent.metadata,
168
- "memory_table": agent.db.memory_table_name if agent.db and agent.enable_user_memories else None,
170
+ "memory_table": agent.db.memory_table_name if agent.db and agent.update_memory_on_run else None,
169
171
  }
170
172
 
171
173
  if agent.memory_manager.model is not None:
@@ -0,0 +1,3 @@
1
+ from agno.os.routers.components.components import get_components_router
2
+
3
+ __all__ = ["get_components_router"]