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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. agno/agent/__init__.py +4 -0
  2. agno/agent/agent.py +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 +1229 -25
  7. agno/db/postgres/schemas.py +48 -1
  8. agno/db/sqlite/async_sqlite.py +119 -4
  9. agno/db/sqlite/schemas.py +51 -0
  10. agno/db/sqlite/sqlite.py +1173 -13
  11. agno/db/utils.py +37 -1
  12. agno/knowledge/__init__.py +4 -0
  13. agno/knowledge/chunking/code.py +1 -1
  14. agno/knowledge/chunking/semantic.py +1 -1
  15. agno/knowledge/chunking/strategy.py +4 -0
  16. agno/knowledge/filesystem.py +412 -0
  17. agno/knowledge/knowledge.py +2767 -2254
  18. agno/knowledge/protocol.py +134 -0
  19. agno/knowledge/reader/arxiv_reader.py +2 -2
  20. agno/knowledge/reader/base.py +9 -7
  21. agno/knowledge/reader/csv_reader.py +5 -5
  22. agno/knowledge/reader/docx_reader.py +2 -2
  23. agno/knowledge/reader/field_labeled_csv_reader.py +2 -2
  24. agno/knowledge/reader/firecrawl_reader.py +2 -2
  25. agno/knowledge/reader/json_reader.py +2 -2
  26. agno/knowledge/reader/markdown_reader.py +2 -2
  27. agno/knowledge/reader/pdf_reader.py +5 -4
  28. agno/knowledge/reader/pptx_reader.py +2 -2
  29. agno/knowledge/reader/reader_factory.py +110 -0
  30. agno/knowledge/reader/s3_reader.py +2 -2
  31. agno/knowledge/reader/tavily_reader.py +2 -2
  32. agno/knowledge/reader/text_reader.py +2 -2
  33. agno/knowledge/reader/web_search_reader.py +2 -2
  34. agno/knowledge/reader/website_reader.py +5 -3
  35. agno/knowledge/reader/wikipedia_reader.py +2 -2
  36. agno/knowledge/reader/youtube_reader.py +2 -2
  37. agno/knowledge/utils.py +37 -29
  38. agno/learn/__init__.py +6 -0
  39. agno/learn/machine.py +35 -0
  40. agno/learn/schemas.py +82 -11
  41. agno/learn/stores/__init__.py +3 -0
  42. agno/learn/stores/decision_log.py +1156 -0
  43. agno/learn/stores/learned_knowledge.py +6 -6
  44. agno/models/anthropic/claude.py +24 -0
  45. agno/models/aws/bedrock.py +20 -0
  46. agno/models/base.py +48 -4
  47. agno/models/cohere/chat.py +25 -0
  48. agno/models/google/gemini.py +50 -5
  49. agno/models/litellm/chat.py +38 -0
  50. agno/models/openai/chat.py +7 -0
  51. agno/models/openrouter/openrouter.py +46 -0
  52. agno/models/response.py +16 -0
  53. agno/os/app.py +83 -44
  54. agno/os/middleware/__init__.py +2 -0
  55. agno/os/middleware/trailing_slash.py +27 -0
  56. agno/os/router.py +1 -0
  57. agno/os/routers/agents/router.py +29 -16
  58. agno/os/routers/agents/schema.py +6 -4
  59. agno/os/routers/components/__init__.py +3 -0
  60. agno/os/routers/components/components.py +466 -0
  61. agno/os/routers/evals/schemas.py +4 -3
  62. agno/os/routers/health.py +3 -3
  63. agno/os/routers/knowledge/knowledge.py +3 -3
  64. agno/os/routers/memory/schemas.py +4 -2
  65. agno/os/routers/metrics/metrics.py +9 -11
  66. agno/os/routers/metrics/schemas.py +10 -6
  67. agno/os/routers/registry/__init__.py +3 -0
  68. agno/os/routers/registry/registry.py +337 -0
  69. agno/os/routers/teams/router.py +20 -8
  70. agno/os/routers/teams/schema.py +6 -4
  71. agno/os/routers/traces/traces.py +5 -5
  72. agno/os/routers/workflows/router.py +38 -11
  73. agno/os/routers/workflows/schema.py +1 -1
  74. agno/os/schema.py +92 -26
  75. agno/os/utils.py +84 -19
  76. agno/reasoning/anthropic.py +2 -2
  77. agno/reasoning/azure_ai_foundry.py +2 -2
  78. agno/reasoning/deepseek.py +2 -2
  79. agno/reasoning/default.py +6 -7
  80. agno/reasoning/gemini.py +2 -2
  81. agno/reasoning/helpers.py +6 -7
  82. agno/reasoning/manager.py +4 -10
  83. agno/reasoning/ollama.py +2 -2
  84. agno/reasoning/openai.py +2 -2
  85. agno/reasoning/vertexai.py +2 -2
  86. agno/registry/__init__.py +3 -0
  87. agno/registry/registry.py +68 -0
  88. agno/run/agent.py +57 -0
  89. agno/run/base.py +7 -0
  90. agno/run/team.py +57 -0
  91. agno/skills/agent_skills.py +10 -3
  92. agno/team/__init__.py +3 -1
  93. agno/team/team.py +1145 -326
  94. agno/tools/duckduckgo.py +25 -71
  95. agno/tools/exa.py +0 -21
  96. agno/tools/function.py +35 -83
  97. agno/tools/knowledge.py +9 -4
  98. agno/tools/mem0.py +11 -10
  99. agno/tools/memory.py +47 -46
  100. agno/tools/parallel.py +0 -7
  101. agno/tools/reasoning.py +30 -23
  102. agno/tools/tavily.py +4 -1
  103. agno/tools/websearch.py +93 -0
  104. agno/tools/website.py +1 -1
  105. agno/tools/wikipedia.py +1 -1
  106. agno/tools/workflow.py +48 -47
  107. agno/utils/agent.py +42 -5
  108. agno/utils/events.py +160 -2
  109. agno/utils/print_response/agent.py +0 -31
  110. agno/utils/print_response/team.py +0 -2
  111. agno/utils/print_response/workflow.py +0 -2
  112. agno/utils/team.py +61 -11
  113. agno/vectordb/lancedb/lance_db.py +4 -1
  114. agno/vectordb/mongodb/mongodb.py +1 -1
  115. agno/vectordb/qdrant/qdrant.py +4 -4
  116. agno/workflow/__init__.py +3 -1
  117. agno/workflow/condition.py +0 -21
  118. agno/workflow/loop.py +0 -21
  119. agno/workflow/parallel.py +0 -21
  120. agno/workflow/router.py +0 -21
  121. agno/workflow/step.py +117 -24
  122. agno/workflow/steps.py +0 -21
  123. agno/workflow/workflow.py +427 -63
  124. {agno-2.3.26.dist-info → agno-2.4.0.dist-info}/METADATA +46 -76
  125. {agno-2.3.26.dist-info → agno-2.4.0.dist-info}/RECORD +128 -117
  126. {agno-2.3.26.dist-info → agno-2.4.0.dist-info}/WHEEL +0 -0
  127. {agno-2.3.26.dist-info → agno-2.4.0.dist-info}/licenses/LICENSE +0 -0
  128. {agno-2.3.26.dist-info → agno-2.4.0.dist-info}/top_level.txt +0 -0
@@ -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(
@@ -14,6 +14,7 @@ from fastapi import (
14
14
  from fastapi.responses import JSONResponse, StreamingResponse
15
15
  from pydantic import BaseModel
16
16
 
17
+ from agno.db.base import BaseDb
17
18
  from agno.exceptions import InputCheckError, OutputCheckError
18
19
  from agno.os.auth import (
19
20
  get_auth_token_from_request,
@@ -61,7 +62,9 @@ async def handle_workflow_via_websocket(websocket: WebSocket, message: dict, os:
61
62
  return
62
63
 
63
64
  # Get workflow from OS
64
- workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
65
+ workflow = get_workflow_by_id(
66
+ workflow_id=workflow_id, workflows=os.workflows, db=os.db, registry=os.registry, create_fresh=True
67
+ )
65
68
  if not workflow:
66
69
  await websocket.send_text(json.dumps({"event": "error", "error": f"Workflow {workflow_id} not found"}))
67
70
  return
@@ -141,7 +144,9 @@ async def handle_workflow_subscription(websocket: WebSocket, message: dict, os:
141
144
  if buffer_status is None:
142
145
  # Run not in buffer - check database
143
146
  if workflow_id and session_id:
144
- workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
147
+ workflow = get_workflow_by_id(
148
+ workflow_id=workflow_id, workflows=os.workflows, db=os.db, registry=os.registry, create_fresh=True
149
+ )
145
150
  if workflow and isinstance(workflow, Workflow):
146
151
  workflow_run = await workflow.aget_run_output(run_id, session_id)
147
152
 
@@ -526,9 +531,6 @@ def get_workflow_router(
526
531
  },
527
532
  )
528
533
  async def get_workflows(request: Request) -> List[WorkflowSummaryResponse]:
529
- if os.workflows is None:
530
- return []
531
-
532
534
  # Filter workflows based on user's scopes (only if authorization is enabled)
533
535
  if getattr(request.state, "authorization_enabled", False):
534
536
  from agno.os.auth import filter_resources_by_access, get_accessible_resources
@@ -538,11 +540,24 @@ def get_workflow_router(
538
540
  if not accessible_ids:
539
541
  raise HTTPException(status_code=403, detail="Insufficient permissions")
540
542
 
541
- accessible_workflows = filter_resources_by_access(request, os.workflows, "workflows")
543
+ accessible_workflows = filter_resources_by_access(request, os.workflows or [], "workflows")
542
544
  else:
543
- accessible_workflows = os.workflows
545
+ accessible_workflows = os.workflows or []
546
+
547
+ workflows: List[WorkflowSummaryResponse] = []
548
+ if accessible_workflows:
549
+ for workflow in accessible_workflows:
550
+ workflows.append(WorkflowSummaryResponse.from_workflow(workflow=workflow))
551
+
552
+ if os.db and isinstance(os.db, BaseDb):
553
+ from agno.workflow.workflow import get_workflows
544
554
 
545
- return [WorkflowSummaryResponse.from_workflow(workflow) for workflow in accessible_workflows]
555
+ db_workflows = get_workflows(db=os.db, registry=os.registry)
556
+ if db_workflows:
557
+ for db_workflow in db_workflows:
558
+ workflows.append(WorkflowSummaryResponse.from_workflow(workflow=db_workflow))
559
+
560
+ return workflows
546
561
 
547
562
  @router.get(
548
563
  "/workflows/{workflow_id}",
@@ -571,7 +586,9 @@ def get_workflow_router(
571
586
  dependencies=[Depends(require_resource_access("workflows", "read", "workflow_id"))],
572
587
  )
573
588
  async def get_workflow(workflow_id: str, request: Request) -> WorkflowResponse:
574
- workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
589
+ workflow = get_workflow_by_id(
590
+ workflow_id=workflow_id, workflows=os.workflows, db=os.db, registry=os.registry, create_fresh=True
591
+ )
575
592
  if workflow is None:
576
593
  raise HTTPException(status_code=404, detail="Workflow not found")
577
594
  if isinstance(workflow, RemoteWorkflow):
@@ -622,6 +639,7 @@ def get_workflow_router(
622
639
  stream: bool = Form(True),
623
640
  session_id: Optional[str] = Form(None),
624
641
  user_id: Optional[str] = Form(None),
642
+ version: Optional[int] = Form(None),
625
643
  ):
626
644
  kwargs = await get_request_kwargs(request, create_workflow_run)
627
645
 
@@ -650,7 +668,14 @@ def get_workflow_router(
650
668
  kwargs["metadata"] = metadata
651
669
 
652
670
  # Retrieve the workflow by ID
653
- workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
671
+ workflow = get_workflow_by_id(
672
+ workflow_id=workflow_id,
673
+ workflows=os.workflows,
674
+ db=os.db,
675
+ version=version,
676
+ registry=os.registry,
677
+ create_fresh=True,
678
+ )
654
679
  if workflow is None:
655
680
  raise HTTPException(status_code=404, detail="Workflow not found")
656
681
 
@@ -716,7 +741,9 @@ def get_workflow_router(
716
741
  dependencies=[Depends(require_resource_access("workflows", "run", "workflow_id"))],
717
742
  )
718
743
  async def cancel_workflow_run(workflow_id: str, run_id: str):
719
- workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
744
+ workflow = get_workflow_by_id(
745
+ workflow_id=workflow_id, workflows=os.workflows, db=os.db, registry=os.registry, create_fresh=True
746
+ )
720
747
 
721
748
  if workflow is None:
722
749
  raise HTTPException(status_code=404, detail="Workflow not found")
@@ -121,7 +121,7 @@ class WorkflowResponse(BaseModel):
121
121
 
122
122
  @classmethod
123
123
  async def from_workflow(cls, workflow: Workflow) -> "WorkflowResponse":
124
- workflow_dict = workflow.to_dict()
124
+ workflow_dict = workflow.to_dict_for_steps()
125
125
  steps = workflow_dict.get("steps")
126
126
 
127
127
  if steps: