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
@@ -170,9 +170,21 @@ class VectorSearchRequestSchema(BaseModel):
170
170
  )
171
171
 
172
172
 
173
+ class RemoteContentSourceSchema(BaseModel):
174
+ """Schema for remote content source configuration."""
175
+
176
+ id: str = Field(..., description="Unique identifier for the content source")
177
+ name: str = Field(..., description="Display name for the content source")
178
+ type: str = Field(..., description="Type of content source (s3, gcs, sharepoint, github)")
179
+ metadata: Optional[Dict[str, Any]] = Field(None, description="Custom metadata for the content source")
180
+
181
+
173
182
  class ConfigResponseSchema(BaseModel):
174
183
  readers: Optional[Dict[str, ReaderSchema]] = Field(None, description="Available content readers")
175
184
  readersForType: Optional[Dict[str, List[str]]] = Field(None, description="Mapping of content types to reader IDs")
176
185
  chunkers: Optional[Dict[str, ChunkerSchema]] = Field(None, description="Available chunking strategies")
177
186
  filters: Optional[List[str]] = Field(None, description="Available filter tags")
178
187
  vector_dbs: Optional[List[VectorDbSchema]] = Field(None, description="Configured vector databases")
188
+ remote_content_sources: Optional[List[RemoteContentSourceSchema]] = Field(
189
+ None, description="Configured remote content sources (S3, GCS, SharePoint, GitHub)"
190
+ )
@@ -1,9 +1,11 @@
1
1
  import json
2
- from datetime import datetime, timezone
2
+ from datetime import datetime
3
3
  from typing import Any, Dict, List, Optional
4
4
 
5
5
  from pydantic import BaseModel, Field
6
6
 
7
+ from agno.os.utils import to_utc_datetime
8
+
7
9
 
8
10
  class DeleteMemoriesRequest(BaseModel):
9
11
  memory_ids: List[str] = Field(..., description="List of memory IDs to delete", min_length=1)
@@ -71,7 +73,7 @@ class UserStatsSchema(BaseModel):
71
73
  return cls(
72
74
  user_id=str(user_stats_dict["user_id"]),
73
75
  total_memories=user_stats_dict["total_memories"],
74
- last_memory_updated_at=datetime.fromtimestamp(updated_at, tz=timezone.utc) if updated_at else None,
76
+ last_memory_updated_at=to_utc_datetime(updated_at),
75
77
  )
76
78
 
77
79
 
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from datetime import date, datetime, timezone
2
+ from datetime import date
3
3
  from typing import List, Optional, Union, cast
4
4
 
5
5
  from fastapi import Depends, HTTPException, Query, Request
@@ -16,7 +16,7 @@ from agno.os.schema import (
16
16
  ValidationErrorResponse,
17
17
  )
18
18
  from agno.os.settings import AgnoAPISettings
19
- from agno.os.utils import get_db
19
+ from agno.os.utils import get_db, to_utc_datetime
20
20
  from agno.remote.base import RemoteDb
21
21
 
22
22
  logger = logging.getLogger(__name__)
@@ -79,9 +79,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
79
79
  "reasoning_tokens": 0,
80
80
  },
81
81
  "model_metrics": [{"model_id": "gpt-4o", "model_provider": "OpenAI", "count": 5}],
82
- "date": "2025-07-31T00:00:00",
83
- "created_at": 1753993132,
84
- "updated_at": 1753993741,
82
+ "date": "2025-07-31T00:00:00Z",
83
+ "created_at": "2025-07-31T12:38:52Z",
84
+ "updated_at": "2025-07-31T12:49:01Z",
85
85
  }
86
86
  ]
87
87
  }
@@ -121,9 +121,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
121
121
 
122
122
  return MetricsResponse(
123
123
  metrics=[DayAggregatedMetrics.from_dict(metric) for metric in metrics],
124
- updated_at=datetime.fromtimestamp(latest_updated_at, tz=timezone.utc)
125
- if latest_updated_at is not None
126
- else None,
124
+ updated_at=to_utc_datetime(latest_updated_at),
127
125
  )
128
126
 
129
127
  except Exception as e:
@@ -167,9 +165,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
167
165
  "reasoning_tokens": 0,
168
166
  },
169
167
  "model_metrics": [{"model_id": "gpt-4o", "model_provider": "OpenAI", "count": 2}],
170
- "date": "2025-08-12T00:00:00",
171
- "created_at": 1755016907,
172
- "updated_at": 1755016907,
168
+ "date": "2025-08-12T00:00:00Z",
169
+ "created_at": "2025-08-12T08:01:47Z",
170
+ "updated_at": "2025-08-12T08:01:47Z",
173
171
  }
174
172
  ]
175
173
  }
@@ -1,8 +1,10 @@
1
- from datetime import datetime
1
+ from datetime import datetime, timezone
2
2
  from typing import Any, Dict, List, Optional
3
3
 
4
4
  from pydantic import BaseModel, Field
5
5
 
6
+ from agno.os.utils import to_utc_datetime
7
+
6
8
 
7
9
  class DayAggregatedMetrics(BaseModel):
8
10
  """Aggregated metrics for a given day"""
@@ -20,22 +22,24 @@ class DayAggregatedMetrics(BaseModel):
20
22
  model_metrics: List[Dict[str, Any]] = Field(..., description="Metrics grouped by model (model_id, provider, count)")
21
23
 
22
24
  date: datetime = Field(..., description="Date for which these metrics are aggregated")
23
- created_at: int = Field(..., description="Unix timestamp when metrics were created", ge=0)
24
- updated_at: int = Field(..., description="Unix timestamp when metrics were last updated", ge=0)
25
+ created_at: datetime = Field(..., description="Timestamp when metrics were created")
26
+ updated_at: datetime = Field(..., description="Timestamp when metrics were last updated")
25
27
 
26
28
  @classmethod
27
29
  def from_dict(cls, metrics_dict: Dict[str, Any]) -> "DayAggregatedMetrics":
30
+ created_at = to_utc_datetime(metrics_dict.get("created_at")) or datetime.now(timezone.utc)
31
+ updated_at = to_utc_datetime(metrics_dict.get("updated_at", created_at)) or created_at
28
32
  return cls(
29
33
  agent_runs_count=metrics_dict.get("agent_runs_count", 0),
30
34
  agent_sessions_count=metrics_dict.get("agent_sessions_count", 0),
31
- created_at=metrics_dict.get("created_at", 0),
32
- date=metrics_dict.get("date", datetime.now()),
35
+ date=metrics_dict.get("date", datetime.now(timezone.utc)),
33
36
  id=metrics_dict.get("id", ""),
34
37
  model_metrics=metrics_dict.get("model_metrics", {}),
35
38
  team_runs_count=metrics_dict.get("team_runs_count", 0),
36
39
  team_sessions_count=metrics_dict.get("team_sessions_count", 0),
37
40
  token_metrics=metrics_dict.get("token_metrics", {}),
38
- updated_at=metrics_dict.get("updated_at", metrics_dict.get("created_at", 0)),
41
+ created_at=created_at,
42
+ updated_at=updated_at,
39
43
  users_count=metrics_dict.get("users_count", 0),
40
44
  workflow_runs_count=metrics_dict.get("workflow_runs_count", 0),
41
45
  workflow_sessions_count=metrics_dict.get("workflow_sessions_count", 0),
@@ -0,0 +1,3 @@
1
+ from agno.os.routers.registry.registry import get_registry_router
2
+
3
+ __all__ = ["get_registry_router"]
@@ -0,0 +1,337 @@
1
+ import time
2
+ from typing import Any, Dict, List, Literal, Optional
3
+
4
+ from fastapi import APIRouter, Depends, HTTPException, Query
5
+ from pydantic import BaseModel, Field
6
+
7
+ from agno.os.auth import get_authentication_dependency
8
+ from agno.os.schema import (
9
+ BadRequestResponse,
10
+ InternalServerErrorResponse,
11
+ NotFoundResponse,
12
+ PaginatedResponse,
13
+ PaginationInfo,
14
+ UnauthenticatedResponse,
15
+ ValidationErrorResponse,
16
+ )
17
+ from agno.os.settings import AgnoAPISettings
18
+ from agno.registry import Registry
19
+ from agno.tools.function import Function
20
+ from agno.tools.toolkit import Toolkit
21
+ from agno.utils.log import log_error
22
+
23
+ ComponentType = Literal["tool", "toolkit", "model", "db", "vector_db", "schema"]
24
+
25
+
26
+ # ============================================
27
+ # Response Schema
28
+ # ============================================
29
+
30
+
31
+ class ComponentResponse(BaseModel):
32
+ name: str
33
+ component_type: ComponentType
34
+ description: Optional[str] = None
35
+ metadata: Dict[str, Any] = Field(default_factory=dict)
36
+ # Tool-specific fields (matching config format)
37
+ parameters: Optional[Dict[str, Any]] = None
38
+ requires_confirmation: Optional[bool] = None
39
+ external_execution: Optional[bool] = None
40
+
41
+
42
+ # ============================================
43
+ # Router
44
+ # ============================================
45
+
46
+
47
+ def get_registry_router(registry: Registry, settings: AgnoAPISettings = AgnoAPISettings()) -> APIRouter:
48
+ router = APIRouter(
49
+ dependencies=[Depends(get_authentication_dependency(settings))],
50
+ tags=["Registry"],
51
+ responses={
52
+ 400: {"description": "Bad Request", "model": BadRequestResponse},
53
+ 401: {"description": "Unauthorized", "model": UnauthenticatedResponse},
54
+ 404: {"description": "Not Found", "model": NotFoundResponse},
55
+ 422: {"description": "Validation Error", "model": ValidationErrorResponse},
56
+ 500: {"description": "Internal Server Error", "model": InternalServerErrorResponse},
57
+ },
58
+ )
59
+ return attach_routes(router=router, registry=registry)
60
+
61
+
62
+ def attach_routes(router: APIRouter, registry: Registry) -> APIRouter:
63
+ def _safe_str(v: Any) -> Optional[str]:
64
+ if v is None:
65
+ return None
66
+ if isinstance(v, str):
67
+ s = v.strip()
68
+ return s or None
69
+ return str(v)
70
+
71
+ def _safe_name(obj: Any, fallback: str) -> str:
72
+ n = getattr(obj, "name", None)
73
+ n = _safe_str(n)
74
+ return n or fallback
75
+
76
+ def _class_path(obj: Any) -> str:
77
+ cls = obj.__class__
78
+ return f"{cls.__module__}.{cls.__name__}"
79
+
80
+ def _maybe_jsonable(value: Any) -> Any:
81
+ # Best-effort: keep only data that is likely JSON serializable
82
+ # If your Function.parameters is a Pydantic model or custom object, this avoids 500s.
83
+ if value is None:
84
+ return None
85
+ if isinstance(value, (str, int, float, bool)):
86
+ return value
87
+ if isinstance(value, list):
88
+ return [_maybe_jsonable(x) for x in value]
89
+ if isinstance(value, dict):
90
+ out: Dict[str, Any] = {}
91
+ for k, v in value.items():
92
+ out[str(k)] = _maybe_jsonable(v)
93
+ return out
94
+ # Fallback to string to avoid serialization errors
95
+ return str(value)
96
+
97
+ def _get_all_components(include_schema: bool) -> List[ComponentResponse]:
98
+ components: List[ComponentResponse] = []
99
+
100
+ # Tools
101
+ for tool in getattr(registry, "tools", []) or []:
102
+ if isinstance(tool, Toolkit):
103
+ toolkit_name = _safe_name(tool, fallback=tool.__class__.__name__)
104
+ functions = getattr(tool, "functions", {}) or {}
105
+
106
+ components.append(
107
+ ComponentResponse(
108
+ name=toolkit_name,
109
+ component_type="toolkit",
110
+ description=_safe_str(getattr(tool, "description", None)),
111
+ metadata={
112
+ "class_path": _class_path(tool),
113
+ "functions": sorted(list(functions.keys())),
114
+ },
115
+ )
116
+ )
117
+
118
+ # Also expose individual functions within toolkit
119
+ for func in functions.values():
120
+ func_name = _safe_name(func, fallback=func.__class__.__name__)
121
+ # Check if function requires confirmation or external execution
122
+ # First check function-level settings, then toolkit-level settings
123
+ requires_confirmation = getattr(func, "requires_confirmation", None)
124
+ external_execution = getattr(func, "external_execution", None)
125
+
126
+ # If not set on function, check toolkit settings
127
+ if requires_confirmation is None and hasattr(tool, "requires_confirmation_tools"):
128
+ requires_confirmation = func_name in (tool.requires_confirmation_tools or [])
129
+ if external_execution is None and hasattr(tool, "external_execution_required_tools"):
130
+ external_execution = func_name in (tool.external_execution_required_tools or [])
131
+
132
+ # Get parameters - ensure they're processed if needed
133
+ func_params = func.parameters
134
+ # If parameters are empty/default and function has entrypoint, try to process it
135
+ default_params = {"type": "object", "properties": {}, "required": []}
136
+ if func_params == default_params and func.entrypoint and not func.skip_entrypoint_processing:
137
+ try:
138
+ # Create a copy to avoid modifying the original
139
+ func_copy = func.model_copy(deep=False)
140
+ func_copy.process_entrypoint(strict=False)
141
+ func_params = func_copy.parameters
142
+ except Exception:
143
+ # If processing fails, use original parameters
144
+ pass
145
+
146
+ components.append(
147
+ ComponentResponse(
148
+ name=func_name,
149
+ component_type="tool",
150
+ description=_safe_str(getattr(func, "description", None)),
151
+ parameters=_maybe_jsonable(func_params),
152
+ requires_confirmation=requires_confirmation,
153
+ external_execution=external_execution,
154
+ metadata={
155
+ "class_path": _class_path(func),
156
+ "toolkit": toolkit_name,
157
+ "has_entrypoint": bool(getattr(func, "entrypoint", None)),
158
+ },
159
+ )
160
+ )
161
+
162
+ elif isinstance(tool, Function):
163
+ func_name = _safe_name(tool, fallback=tool.__class__.__name__)
164
+ requires_confirmation = getattr(tool, "requires_confirmation", None)
165
+ external_execution = getattr(tool, "external_execution", None)
166
+
167
+ # Get parameters - ensure they're processed if needed
168
+ func_params = tool.parameters
169
+ # If parameters are empty/default and function has entrypoint, try to process it
170
+ default_params = {"type": "object", "properties": {}, "required": []}
171
+ if func_params == default_params and tool.entrypoint and not tool.skip_entrypoint_processing:
172
+ try:
173
+ # Create a copy to avoid modifying the original
174
+ tool_copy = tool.model_copy(deep=False)
175
+ tool_copy.process_entrypoint(strict=False)
176
+ func_params = tool_copy.parameters
177
+ except Exception:
178
+ # If processing fails, use original parameters
179
+ pass
180
+
181
+ components.append(
182
+ ComponentResponse(
183
+ name=func_name,
184
+ component_type="tool",
185
+ description=_safe_str(getattr(tool, "description", None)),
186
+ parameters=_maybe_jsonable(func_params),
187
+ requires_confirmation=requires_confirmation,
188
+ external_execution=external_execution,
189
+ metadata={
190
+ "class_path": _class_path(tool),
191
+ "has_entrypoint": bool(getattr(tool, "entrypoint", None)),
192
+ },
193
+ )
194
+ )
195
+
196
+ elif callable(tool):
197
+ call_name = getattr(tool, "__name__", None) or tool.__class__.__name__
198
+ components.append(
199
+ ComponentResponse(
200
+ name=str(call_name),
201
+ component_type="tool",
202
+ description=_safe_str(getattr(tool, "__doc__", None)),
203
+ metadata={"class_path": _class_path(tool)},
204
+ )
205
+ )
206
+
207
+ # Models
208
+ for model in getattr(registry, "models", []) or []:
209
+ model_name = (
210
+ _safe_str(getattr(model, "id", None))
211
+ or _safe_str(getattr(model, "name", None))
212
+ or model.__class__.__name__
213
+ )
214
+ components.append(
215
+ ComponentResponse(
216
+ name=model_name,
217
+ component_type="model",
218
+ description=_safe_str(getattr(model, "description", None)),
219
+ metadata={
220
+ "class_path": _class_path(model),
221
+ "provider": _safe_str(getattr(model, "provider", None)),
222
+ "model_id": _safe_str(getattr(model, "id", None)),
223
+ },
224
+ )
225
+ )
226
+
227
+ # Databases
228
+ for db in getattr(registry, "dbs", []) or []:
229
+ db_name = (
230
+ _safe_str(getattr(db, "name", None))
231
+ or _safe_str(getattr(db, "id", None))
232
+ or _safe_str(getattr(db, "table_name", None))
233
+ or db.__class__.__name__
234
+ )
235
+ components.append(
236
+ ComponentResponse(
237
+ name=db_name,
238
+ component_type="db",
239
+ metadata={
240
+ "class_path": _class_path(db),
241
+ "db_id": _safe_str(getattr(db, "id", None)),
242
+ "table_name": _safe_str(getattr(db, "table_name", None)),
243
+ },
244
+ )
245
+ )
246
+
247
+ # Vector databases
248
+ for vdb in getattr(registry, "vector_dbs", []) or []:
249
+ vdb_name = (
250
+ _safe_str(getattr(vdb, "name", None))
251
+ or _safe_str(getattr(vdb, "id", None))
252
+ or _safe_str(getattr(vdb, "collection", None))
253
+ or _safe_str(getattr(vdb, "table_name", None))
254
+ or vdb.__class__.__name__
255
+ )
256
+ components.append(
257
+ ComponentResponse(
258
+ name=vdb_name,
259
+ component_type="vector_db",
260
+ metadata={
261
+ "class_path": _class_path(vdb),
262
+ "vector_db_id": _safe_str(getattr(vdb, "id", None)),
263
+ "collection": _safe_str(getattr(vdb, "collection", None)),
264
+ "table_name": _safe_str(getattr(vdb, "table_name", None)),
265
+ },
266
+ )
267
+ )
268
+
269
+ # Schemas
270
+ for schema in getattr(registry, "schemas", []) or []:
271
+ schema_name = schema.__name__
272
+ meta: Dict[str, Any] = {"class_path": _class_path(schema)}
273
+ if include_schema:
274
+ try:
275
+ meta["schema"] = schema.model_json_schema() if hasattr(schema, "model_json_schema") else {}
276
+ except Exception as e:
277
+ meta["schema_error"] = str(e)
278
+
279
+ components.append(
280
+ ComponentResponse(
281
+ name=schema_name,
282
+ component_type="schema",
283
+ metadata=meta,
284
+ )
285
+ )
286
+
287
+ # Stable ordering helps pagination
288
+ components.sort(key=lambda c: (c.component_type, c.name))
289
+ return components
290
+
291
+ @router.get(
292
+ "/registry",
293
+ response_model=PaginatedResponse[ComponentResponse],
294
+ response_model_exclude_none=True,
295
+ status_code=200,
296
+ operation_id="list_registry",
297
+ summary="List Registry",
298
+ description="List all components in the registry with optional filtering.",
299
+ )
300
+ async def list_registry(
301
+ component_type: Optional[ComponentType] = Query(None, description="Filter by component type"),
302
+ name: Optional[str] = Query(None, description="Filter by name (partial match)"),
303
+ include_schema: bool = Query(False, description="Include JSON schema for schema components"),
304
+ page: int = Query(1, ge=1, description="Page number"),
305
+ limit: int = Query(20, ge=1, le=100, description="Items per page"),
306
+ ) -> PaginatedResponse[ComponentResponse]:
307
+ try:
308
+ start_time_ms = time.time() * 1000
309
+ components = _get_all_components(include_schema=include_schema)
310
+
311
+ if component_type:
312
+ components = [c for c in components if c.component_type == component_type]
313
+
314
+ if name:
315
+ needle = name.lower().strip()
316
+ components = [c for c in components if needle in c.name.lower()]
317
+
318
+ total_count = len(components)
319
+ total_pages = (total_count + limit - 1) // limit if limit > 0 else 0
320
+ start_idx = (page - 1) * limit
321
+ paginated = components[start_idx : start_idx + limit]
322
+
323
+ return PaginatedResponse(
324
+ data=paginated,
325
+ meta=PaginationInfo(
326
+ page=page,
327
+ limit=limit,
328
+ total_pages=total_pages,
329
+ total_count=total_count,
330
+ search_time_ms=round(time.time() * 1000 - start_time_ms, 2),
331
+ ),
332
+ )
333
+ except Exception as e:
334
+ log_error(f"Error listing components: {e}")
335
+ raise HTTPException(status_code=500, detail=str(e))
336
+
337
+ return router
@@ -13,6 +13,7 @@ from fastapi import (
13
13
  )
14
14
  from fastapi.responses import JSONResponse, StreamingResponse
15
15
 
16
+ from agno.db.base import BaseDb
16
17
  from agno.exceptions import InputCheckError, OutputCheckError
17
18
  from agno.media import Audio, Image, Video
18
19
  from agno.media import File as FileMedia
@@ -35,6 +36,7 @@ from agno.os.utils import (
35
36
  process_image,
36
37
  process_video,
37
38
  )
39
+ from agno.registry import Registry
38
40
  from agno.run.team import RunErrorEvent as TeamRunErrorEvent
39
41
  from agno.team.remote import RemoteTeam
40
42
  from agno.team.team import Team
@@ -111,6 +113,7 @@ async def team_response_streamer(
111
113
  def get_team_router(
112
114
  os: "AgentOS",
113
115
  settings: AgnoAPISettings = AgnoAPISettings(),
116
+ registry: Optional[Registry] = None,
114
117
  ) -> APIRouter:
115
118
  """Create the team router with comprehensive OpenAPI documentation."""
116
119
  router = APIRouter(
@@ -165,6 +168,7 @@ def get_team_router(
165
168
  session_id: Optional[str] = Form(None),
166
169
  user_id: Optional[str] = Form(None),
167
170
  files: Optional[List[UploadFile]] = File(None),
171
+ version: Optional[int] = Form(None),
168
172
  ):
169
173
  kwargs = await get_request_kwargs(request, create_team_run)
170
174
 
@@ -194,7 +198,9 @@ def get_team_router(
194
198
 
195
199
  logger.debug(f"Creating team run: {message=} {session_id=} {monitor=} {user_id=} {team_id=} {files=} {kwargs=}")
196
200
 
197
- team = get_team_by_id(team_id, os.teams, create_fresh=True)
201
+ team = get_team_by_id(
202
+ team_id=team_id, teams=os.teams, db=os.db, version=version, registry=registry, create_fresh=True
203
+ )
198
204
  if team is None:
199
205
  raise HTTPException(status_code=404, detail="Team not found")
200
206
 
@@ -321,7 +327,7 @@ def get_team_router(
321
327
  team_id: str,
322
328
  run_id: str,
323
329
  ):
324
- team = get_team_by_id(team_id, os.teams, create_fresh=True)
330
+ team = get_team_by_id(team_id=team_id, teams=os.teams, db=os.db, registry=registry, create_fresh=True)
325
331
  if team is None:
326
332
  raise HTTPException(status_code=404, detail="Team not found")
327
333
 
@@ -414,9 +420,6 @@ def get_team_router(
414
420
  )
415
421
  async def get_teams(request: Request) -> List[TeamResponse]:
416
422
  """Return the list of all Teams present in the contextual OS"""
417
- if os.teams is None:
418
- return []
419
-
420
423
  # Filter teams based on user's scopes (only if authorization is enabled)
421
424
  if getattr(request.state, "authorization_enabled", False):
422
425
  from agno.os.auth import filter_resources_by_access, get_accessible_resources
@@ -426,9 +429,9 @@ def get_team_router(
426
429
  if not accessible_ids:
427
430
  raise HTTPException(status_code=403, detail="Insufficient permissions")
428
431
 
429
- accessible_teams = filter_resources_by_access(request, os.teams, "teams")
432
+ accessible_teams = filter_resources_by_access(request, os.teams or [], "teams")
430
433
  else:
431
- accessible_teams = os.teams
434
+ accessible_teams = os.teams or []
432
435
 
433
436
  teams = []
434
437
  for team in accessible_teams:
@@ -438,6 +441,15 @@ def get_team_router(
438
441
  team_response = await TeamResponse.from_team(team=team)
439
442
  teams.append(team_response)
440
443
 
444
+ # Also load teams from database
445
+ if os.db and isinstance(os.db, BaseDb):
446
+ from agno.team.team import get_teams
447
+
448
+ db_teams = get_teams(db=os.db, registry=registry)
449
+ for db_team in db_teams:
450
+ team_response = await TeamResponse.from_team(team=db_team)
451
+ teams.append(team_response)
452
+
441
453
  return teams
442
454
 
443
455
  @router.get(
@@ -526,7 +538,7 @@ def get_team_router(
526
538
  dependencies=[Depends(require_resource_access("teams", "read", "team_id"))],
527
539
  )
528
540
  async def get_team(team_id: str, request: Request) -> TeamResponse:
529
- team = get_team_by_id(team_id, os.teams, create_fresh=True)
541
+ team = get_team_by_id(team_id=team_id, teams=os.teams, db=os.db, registry=registry, create_fresh=True)
530
542
  if team is None:
531
543
  raise HTTPException(status_code=404, detail="Team not found")
532
544
 
@@ -65,7 +65,7 @@ class TeamResponse(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,
@@ -137,8 +137,9 @@ class TeamResponse(BaseModel):
137
137
  "cache_session": team.cache_session,
138
138
  }
139
139
 
140
+ contents_db = getattr(team.knowledge, "contents_db", None) if team.knowledge else None
140
141
  knowledge_info = {
141
- "db_id": team.knowledge.contents_db.id if team.knowledge and team.knowledge.contents_db else None,
142
+ "db_id": contents_db.id if contents_db else None,
142
143
  "knowledge_table": knowledge_table,
143
144
  "enable_agentic_knowledge_filters": team.enable_agentic_knowledge_filters,
144
145
  "knowledge_filters": team.knowledge_filters,
@@ -149,9 +150,10 @@ class TeamResponse(BaseModel):
149
150
  if team.memory_manager is not None:
150
151
  memory_info = {
151
152
  "enable_agentic_memory": team.enable_agentic_memory,
152
- "enable_user_memories": team.enable_user_memories,
153
+ "update_memory_on_run": team.update_memory_on_run,
154
+ "enable_user_memories": team.enable_user_memories, # Soon to be deprecated. Use update_memory_on_run
153
155
  "metadata": team.metadata,
154
- "memory_table": team.db.memory_table_name if team.db and team.enable_user_memories else None,
156
+ "memory_table": team.db.memory_table_name if team.db and team.update_memory_on_run else None,
155
157
  }
156
158
 
157
159
  if team.memory_manager.model is not None:
@@ -22,7 +22,7 @@ from agno.os.schema import (
22
22
  ValidationErrorResponse,
23
23
  )
24
24
  from agno.os.settings import AgnoAPISettings
25
- from agno.os.utils import get_db, parse_datetime_to_utc
25
+ from agno.os.utils import get_db, timestamp_to_datetime
26
26
  from agno.remote.base import RemoteDb
27
27
  from agno.utils.log import log_error
28
28
 
@@ -159,8 +159,8 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
159
159
  start_time_ms = time_module.time() * 1000
160
160
 
161
161
  # Convert ISO datetime strings to UTC datetime objects
162
- start_time_dt = parse_datetime_to_utc(start_time, "start_time") if start_time else None
163
- end_time_dt = parse_datetime_to_utc(end_time, "end_time") if end_time else None
162
+ start_time_dt = timestamp_to_datetime(start_time, "start_time") if start_time else None
163
+ end_time_dt = timestamp_to_datetime(end_time, "end_time") if end_time else None
164
164
 
165
165
  if isinstance(db, AsyncBaseDb):
166
166
  traces, total_count = await db.get_traces(
@@ -484,8 +484,8 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
484
484
  start_time_ms = time_module.time() * 1000
485
485
 
486
486
  # Convert ISO datetime strings to UTC datetime objects
487
- start_time_dt = parse_datetime_to_utc(start_time, "start_time") if start_time else None
488
- end_time_dt = parse_datetime_to_utc(end_time, "end_time") if end_time else None
487
+ start_time_dt = timestamp_to_datetime(start_time, "start_time") if start_time else None
488
+ end_time_dt = timestamp_to_datetime(end_time, "end_time") if end_time else None
489
489
 
490
490
  if isinstance(db, AsyncBaseDb):
491
491
  stats_list, total_count = await db.get_trace_stats(