agno 2.2.13__py3-none-any.whl → 2.4.3__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 (383) hide show
  1. agno/agent/__init__.py +6 -0
  2. agno/agent/agent.py +5252 -3145
  3. agno/agent/remote.py +525 -0
  4. agno/api/api.py +2 -0
  5. agno/client/__init__.py +3 -0
  6. agno/client/a2a/__init__.py +10 -0
  7. agno/client/a2a/client.py +554 -0
  8. agno/client/a2a/schemas.py +112 -0
  9. agno/client/a2a/utils.py +369 -0
  10. agno/client/os.py +2669 -0
  11. agno/compression/__init__.py +3 -0
  12. agno/compression/manager.py +247 -0
  13. agno/culture/manager.py +2 -2
  14. agno/db/base.py +927 -6
  15. agno/db/dynamo/dynamo.py +788 -2
  16. agno/db/dynamo/schemas.py +128 -0
  17. agno/db/dynamo/utils.py +26 -3
  18. agno/db/firestore/firestore.py +674 -50
  19. agno/db/firestore/schemas.py +41 -0
  20. agno/db/firestore/utils.py +25 -10
  21. agno/db/gcs_json/gcs_json_db.py +506 -3
  22. agno/db/gcs_json/utils.py +14 -2
  23. agno/db/in_memory/in_memory_db.py +203 -4
  24. agno/db/in_memory/utils.py +14 -2
  25. agno/db/json/json_db.py +498 -2
  26. agno/db/json/utils.py +14 -2
  27. agno/db/migrations/manager.py +199 -0
  28. agno/db/migrations/utils.py +19 -0
  29. agno/db/migrations/v1_to_v2.py +54 -16
  30. agno/db/migrations/versions/__init__.py +0 -0
  31. agno/db/migrations/versions/v2_3_0.py +977 -0
  32. agno/db/mongo/async_mongo.py +1013 -39
  33. agno/db/mongo/mongo.py +684 -4
  34. agno/db/mongo/schemas.py +48 -0
  35. agno/db/mongo/utils.py +17 -0
  36. agno/db/mysql/__init__.py +2 -1
  37. agno/db/mysql/async_mysql.py +2958 -0
  38. agno/db/mysql/mysql.py +722 -53
  39. agno/db/mysql/schemas.py +77 -11
  40. agno/db/mysql/utils.py +151 -8
  41. agno/db/postgres/async_postgres.py +1254 -137
  42. agno/db/postgres/postgres.py +2316 -93
  43. agno/db/postgres/schemas.py +153 -21
  44. agno/db/postgres/utils.py +22 -7
  45. agno/db/redis/redis.py +531 -3
  46. agno/db/redis/schemas.py +36 -0
  47. agno/db/redis/utils.py +31 -15
  48. agno/db/schemas/evals.py +1 -0
  49. agno/db/schemas/memory.py +20 -9
  50. agno/db/singlestore/schemas.py +70 -1
  51. agno/db/singlestore/singlestore.py +737 -74
  52. agno/db/singlestore/utils.py +13 -3
  53. agno/db/sqlite/async_sqlite.py +1069 -89
  54. agno/db/sqlite/schemas.py +133 -1
  55. agno/db/sqlite/sqlite.py +2203 -165
  56. agno/db/sqlite/utils.py +21 -11
  57. agno/db/surrealdb/models.py +25 -0
  58. agno/db/surrealdb/surrealdb.py +603 -1
  59. agno/db/utils.py +60 -0
  60. agno/eval/__init__.py +26 -3
  61. agno/eval/accuracy.py +25 -12
  62. agno/eval/agent_as_judge.py +871 -0
  63. agno/eval/base.py +29 -0
  64. agno/eval/performance.py +10 -4
  65. agno/eval/reliability.py +22 -13
  66. agno/eval/utils.py +2 -1
  67. agno/exceptions.py +42 -0
  68. agno/hooks/__init__.py +3 -0
  69. agno/hooks/decorator.py +164 -0
  70. agno/integrations/discord/client.py +13 -2
  71. agno/knowledge/__init__.py +4 -0
  72. agno/knowledge/chunking/code.py +90 -0
  73. agno/knowledge/chunking/document.py +65 -4
  74. agno/knowledge/chunking/fixed.py +4 -1
  75. agno/knowledge/chunking/markdown.py +102 -11
  76. agno/knowledge/chunking/recursive.py +2 -2
  77. agno/knowledge/chunking/semantic.py +130 -48
  78. agno/knowledge/chunking/strategy.py +18 -0
  79. agno/knowledge/embedder/azure_openai.py +0 -1
  80. agno/knowledge/embedder/google.py +1 -1
  81. agno/knowledge/embedder/mistral.py +1 -1
  82. agno/knowledge/embedder/nebius.py +1 -1
  83. agno/knowledge/embedder/openai.py +16 -12
  84. agno/knowledge/filesystem.py +412 -0
  85. agno/knowledge/knowledge.py +4261 -1199
  86. agno/knowledge/protocol.py +134 -0
  87. agno/knowledge/reader/arxiv_reader.py +3 -2
  88. agno/knowledge/reader/base.py +9 -7
  89. agno/knowledge/reader/csv_reader.py +91 -42
  90. agno/knowledge/reader/docx_reader.py +9 -10
  91. agno/knowledge/reader/excel_reader.py +225 -0
  92. agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
  93. agno/knowledge/reader/firecrawl_reader.py +3 -2
  94. agno/knowledge/reader/json_reader.py +16 -22
  95. agno/knowledge/reader/markdown_reader.py +15 -14
  96. agno/knowledge/reader/pdf_reader.py +33 -28
  97. agno/knowledge/reader/pptx_reader.py +9 -10
  98. agno/knowledge/reader/reader_factory.py +135 -1
  99. agno/knowledge/reader/s3_reader.py +8 -16
  100. agno/knowledge/reader/tavily_reader.py +3 -3
  101. agno/knowledge/reader/text_reader.py +15 -14
  102. agno/knowledge/reader/utils/__init__.py +17 -0
  103. agno/knowledge/reader/utils/spreadsheet.py +114 -0
  104. agno/knowledge/reader/web_search_reader.py +8 -65
  105. agno/knowledge/reader/website_reader.py +16 -13
  106. agno/knowledge/reader/wikipedia_reader.py +36 -3
  107. agno/knowledge/reader/youtube_reader.py +3 -2
  108. agno/knowledge/remote_content/__init__.py +33 -0
  109. agno/knowledge/remote_content/config.py +266 -0
  110. agno/knowledge/remote_content/remote_content.py +105 -17
  111. agno/knowledge/utils.py +76 -22
  112. agno/learn/__init__.py +71 -0
  113. agno/learn/config.py +463 -0
  114. agno/learn/curate.py +185 -0
  115. agno/learn/machine.py +725 -0
  116. agno/learn/schemas.py +1114 -0
  117. agno/learn/stores/__init__.py +38 -0
  118. agno/learn/stores/decision_log.py +1156 -0
  119. agno/learn/stores/entity_memory.py +3275 -0
  120. agno/learn/stores/learned_knowledge.py +1583 -0
  121. agno/learn/stores/protocol.py +117 -0
  122. agno/learn/stores/session_context.py +1217 -0
  123. agno/learn/stores/user_memory.py +1495 -0
  124. agno/learn/stores/user_profile.py +1220 -0
  125. agno/learn/utils.py +209 -0
  126. agno/media.py +22 -6
  127. agno/memory/__init__.py +14 -1
  128. agno/memory/manager.py +223 -8
  129. agno/memory/strategies/__init__.py +15 -0
  130. agno/memory/strategies/base.py +66 -0
  131. agno/memory/strategies/summarize.py +196 -0
  132. agno/memory/strategies/types.py +37 -0
  133. agno/models/aimlapi/aimlapi.py +17 -0
  134. agno/models/anthropic/claude.py +434 -59
  135. agno/models/aws/bedrock.py +121 -20
  136. agno/models/aws/claude.py +131 -274
  137. agno/models/azure/ai_foundry.py +10 -6
  138. agno/models/azure/openai_chat.py +33 -10
  139. agno/models/base.py +1162 -561
  140. agno/models/cerebras/cerebras.py +120 -24
  141. agno/models/cerebras/cerebras_openai.py +21 -2
  142. agno/models/cohere/chat.py +65 -6
  143. agno/models/cometapi/cometapi.py +18 -1
  144. agno/models/dashscope/dashscope.py +2 -3
  145. agno/models/deepinfra/deepinfra.py +18 -1
  146. agno/models/deepseek/deepseek.py +69 -3
  147. agno/models/fireworks/fireworks.py +18 -1
  148. agno/models/google/gemini.py +959 -89
  149. agno/models/google/utils.py +22 -0
  150. agno/models/groq/groq.py +48 -18
  151. agno/models/huggingface/huggingface.py +17 -6
  152. agno/models/ibm/watsonx.py +16 -6
  153. agno/models/internlm/internlm.py +18 -1
  154. agno/models/langdb/langdb.py +13 -1
  155. agno/models/litellm/chat.py +88 -9
  156. agno/models/litellm/litellm_openai.py +18 -1
  157. agno/models/message.py +24 -5
  158. agno/models/meta/llama.py +40 -13
  159. agno/models/meta/llama_openai.py +22 -21
  160. agno/models/metrics.py +12 -0
  161. agno/models/mistral/mistral.py +8 -4
  162. agno/models/n1n/__init__.py +3 -0
  163. agno/models/n1n/n1n.py +57 -0
  164. agno/models/nebius/nebius.py +6 -7
  165. agno/models/nvidia/nvidia.py +20 -3
  166. agno/models/ollama/__init__.py +2 -0
  167. agno/models/ollama/chat.py +17 -6
  168. agno/models/ollama/responses.py +100 -0
  169. agno/models/openai/__init__.py +2 -0
  170. agno/models/openai/chat.py +117 -26
  171. agno/models/openai/open_responses.py +46 -0
  172. agno/models/openai/responses.py +110 -32
  173. agno/models/openrouter/__init__.py +2 -0
  174. agno/models/openrouter/openrouter.py +67 -2
  175. agno/models/openrouter/responses.py +146 -0
  176. agno/models/perplexity/perplexity.py +19 -1
  177. agno/models/portkey/portkey.py +7 -6
  178. agno/models/requesty/requesty.py +19 -2
  179. agno/models/response.py +20 -2
  180. agno/models/sambanova/sambanova.py +20 -3
  181. agno/models/siliconflow/siliconflow.py +19 -2
  182. agno/models/together/together.py +20 -3
  183. agno/models/vercel/v0.py +20 -3
  184. agno/models/vertexai/claude.py +124 -4
  185. agno/models/vllm/vllm.py +19 -14
  186. agno/models/xai/xai.py +19 -2
  187. agno/os/app.py +467 -137
  188. agno/os/auth.py +253 -5
  189. agno/os/config.py +22 -0
  190. agno/os/interfaces/a2a/a2a.py +7 -6
  191. agno/os/interfaces/a2a/router.py +635 -26
  192. agno/os/interfaces/a2a/utils.py +32 -33
  193. agno/os/interfaces/agui/agui.py +5 -3
  194. agno/os/interfaces/agui/router.py +26 -16
  195. agno/os/interfaces/agui/utils.py +97 -57
  196. agno/os/interfaces/base.py +7 -7
  197. agno/os/interfaces/slack/router.py +16 -7
  198. agno/os/interfaces/slack/slack.py +7 -7
  199. agno/os/interfaces/whatsapp/router.py +35 -7
  200. agno/os/interfaces/whatsapp/security.py +3 -1
  201. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  202. agno/os/managers.py +326 -0
  203. agno/os/mcp.py +652 -79
  204. agno/os/middleware/__init__.py +4 -0
  205. agno/os/middleware/jwt.py +718 -115
  206. agno/os/middleware/trailing_slash.py +27 -0
  207. agno/os/router.py +105 -1558
  208. agno/os/routers/agents/__init__.py +3 -0
  209. agno/os/routers/agents/router.py +655 -0
  210. agno/os/routers/agents/schema.py +288 -0
  211. agno/os/routers/components/__init__.py +3 -0
  212. agno/os/routers/components/components.py +475 -0
  213. agno/os/routers/database.py +155 -0
  214. agno/os/routers/evals/evals.py +111 -18
  215. agno/os/routers/evals/schemas.py +38 -5
  216. agno/os/routers/evals/utils.py +80 -11
  217. agno/os/routers/health.py +3 -3
  218. agno/os/routers/knowledge/knowledge.py +284 -35
  219. agno/os/routers/knowledge/schemas.py +14 -2
  220. agno/os/routers/memory/memory.py +274 -11
  221. agno/os/routers/memory/schemas.py +44 -3
  222. agno/os/routers/metrics/metrics.py +30 -15
  223. agno/os/routers/metrics/schemas.py +10 -6
  224. agno/os/routers/registry/__init__.py +3 -0
  225. agno/os/routers/registry/registry.py +337 -0
  226. agno/os/routers/session/session.py +143 -14
  227. agno/os/routers/teams/__init__.py +3 -0
  228. agno/os/routers/teams/router.py +550 -0
  229. agno/os/routers/teams/schema.py +280 -0
  230. agno/os/routers/traces/__init__.py +3 -0
  231. agno/os/routers/traces/schemas.py +414 -0
  232. agno/os/routers/traces/traces.py +549 -0
  233. agno/os/routers/workflows/__init__.py +3 -0
  234. agno/os/routers/workflows/router.py +757 -0
  235. agno/os/routers/workflows/schema.py +139 -0
  236. agno/os/schema.py +157 -584
  237. agno/os/scopes.py +469 -0
  238. agno/os/settings.py +3 -0
  239. agno/os/utils.py +574 -185
  240. agno/reasoning/anthropic.py +85 -1
  241. agno/reasoning/azure_ai_foundry.py +93 -1
  242. agno/reasoning/deepseek.py +102 -2
  243. agno/reasoning/default.py +6 -7
  244. agno/reasoning/gemini.py +87 -3
  245. agno/reasoning/groq.py +109 -2
  246. agno/reasoning/helpers.py +6 -7
  247. agno/reasoning/manager.py +1238 -0
  248. agno/reasoning/ollama.py +93 -1
  249. agno/reasoning/openai.py +115 -1
  250. agno/reasoning/vertexai.py +85 -1
  251. agno/registry/__init__.py +3 -0
  252. agno/registry/registry.py +68 -0
  253. agno/remote/__init__.py +3 -0
  254. agno/remote/base.py +581 -0
  255. agno/run/__init__.py +2 -4
  256. agno/run/agent.py +134 -19
  257. agno/run/base.py +49 -1
  258. agno/run/cancel.py +65 -52
  259. agno/run/cancellation_management/__init__.py +9 -0
  260. agno/run/cancellation_management/base.py +78 -0
  261. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  262. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  263. agno/run/requirement.py +181 -0
  264. agno/run/team.py +111 -19
  265. agno/run/workflow.py +2 -1
  266. agno/session/agent.py +57 -92
  267. agno/session/summary.py +1 -1
  268. agno/session/team.py +62 -115
  269. agno/session/workflow.py +353 -57
  270. agno/skills/__init__.py +17 -0
  271. agno/skills/agent_skills.py +377 -0
  272. agno/skills/errors.py +32 -0
  273. agno/skills/loaders/__init__.py +4 -0
  274. agno/skills/loaders/base.py +27 -0
  275. agno/skills/loaders/local.py +216 -0
  276. agno/skills/skill.py +65 -0
  277. agno/skills/utils.py +107 -0
  278. agno/skills/validator.py +277 -0
  279. agno/table.py +10 -0
  280. agno/team/__init__.py +5 -1
  281. agno/team/remote.py +447 -0
  282. agno/team/team.py +3769 -2202
  283. agno/tools/brandfetch.py +27 -18
  284. agno/tools/browserbase.py +225 -16
  285. agno/tools/crawl4ai.py +3 -0
  286. agno/tools/duckduckgo.py +25 -71
  287. agno/tools/exa.py +0 -21
  288. agno/tools/file.py +14 -13
  289. agno/tools/file_generation.py +12 -6
  290. agno/tools/firecrawl.py +15 -7
  291. agno/tools/function.py +94 -113
  292. agno/tools/google_bigquery.py +11 -2
  293. agno/tools/google_drive.py +4 -3
  294. agno/tools/knowledge.py +9 -4
  295. agno/tools/mcp/mcp.py +301 -18
  296. agno/tools/mcp/multi_mcp.py +269 -14
  297. agno/tools/mem0.py +11 -10
  298. agno/tools/memory.py +47 -46
  299. agno/tools/mlx_transcribe.py +10 -7
  300. agno/tools/models/nebius.py +5 -5
  301. agno/tools/models_labs.py +20 -10
  302. agno/tools/nano_banana.py +151 -0
  303. agno/tools/parallel.py +0 -7
  304. agno/tools/postgres.py +76 -36
  305. agno/tools/python.py +14 -6
  306. agno/tools/reasoning.py +30 -23
  307. agno/tools/redshift.py +406 -0
  308. agno/tools/shopify.py +1519 -0
  309. agno/tools/spotify.py +919 -0
  310. agno/tools/tavily.py +4 -1
  311. agno/tools/toolkit.py +253 -18
  312. agno/tools/websearch.py +93 -0
  313. agno/tools/website.py +1 -1
  314. agno/tools/wikipedia.py +1 -1
  315. agno/tools/workflow.py +56 -48
  316. agno/tools/yfinance.py +12 -11
  317. agno/tracing/__init__.py +12 -0
  318. agno/tracing/exporter.py +161 -0
  319. agno/tracing/schemas.py +276 -0
  320. agno/tracing/setup.py +112 -0
  321. agno/utils/agent.py +251 -10
  322. agno/utils/cryptography.py +22 -0
  323. agno/utils/dttm.py +33 -0
  324. agno/utils/events.py +264 -7
  325. agno/utils/hooks.py +111 -3
  326. agno/utils/http.py +161 -2
  327. agno/utils/mcp.py +49 -8
  328. agno/utils/media.py +22 -1
  329. agno/utils/models/ai_foundry.py +9 -2
  330. agno/utils/models/claude.py +20 -5
  331. agno/utils/models/cohere.py +9 -2
  332. agno/utils/models/llama.py +9 -2
  333. agno/utils/models/mistral.py +4 -2
  334. agno/utils/os.py +0 -0
  335. agno/utils/print_response/agent.py +99 -16
  336. agno/utils/print_response/team.py +223 -24
  337. agno/utils/print_response/workflow.py +0 -2
  338. agno/utils/prompts.py +8 -6
  339. agno/utils/remote.py +23 -0
  340. agno/utils/response.py +1 -13
  341. agno/utils/string.py +91 -2
  342. agno/utils/team.py +62 -12
  343. agno/utils/tokens.py +657 -0
  344. agno/vectordb/base.py +15 -2
  345. agno/vectordb/cassandra/cassandra.py +1 -1
  346. agno/vectordb/chroma/__init__.py +2 -1
  347. agno/vectordb/chroma/chromadb.py +468 -23
  348. agno/vectordb/clickhouse/clickhousedb.py +1 -1
  349. agno/vectordb/couchbase/couchbase.py +6 -2
  350. agno/vectordb/lancedb/lance_db.py +7 -38
  351. agno/vectordb/lightrag/lightrag.py +7 -6
  352. agno/vectordb/milvus/milvus.py +118 -84
  353. agno/vectordb/mongodb/__init__.py +2 -1
  354. agno/vectordb/mongodb/mongodb.py +14 -31
  355. agno/vectordb/pgvector/pgvector.py +120 -66
  356. agno/vectordb/pineconedb/pineconedb.py +2 -19
  357. agno/vectordb/qdrant/__init__.py +2 -1
  358. agno/vectordb/qdrant/qdrant.py +33 -56
  359. agno/vectordb/redis/__init__.py +2 -1
  360. agno/vectordb/redis/redisdb.py +19 -31
  361. agno/vectordb/singlestore/singlestore.py +17 -9
  362. agno/vectordb/surrealdb/surrealdb.py +2 -38
  363. agno/vectordb/weaviate/__init__.py +2 -1
  364. agno/vectordb/weaviate/weaviate.py +7 -3
  365. agno/workflow/__init__.py +5 -1
  366. agno/workflow/agent.py +2 -2
  367. agno/workflow/condition.py +12 -10
  368. agno/workflow/loop.py +28 -9
  369. agno/workflow/parallel.py +21 -13
  370. agno/workflow/remote.py +362 -0
  371. agno/workflow/router.py +12 -9
  372. agno/workflow/step.py +261 -36
  373. agno/workflow/steps.py +12 -8
  374. agno/workflow/types.py +40 -77
  375. agno/workflow/workflow.py +939 -213
  376. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
  377. agno-2.4.3.dist-info/RECORD +677 -0
  378. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
  379. agno/tools/googlesearch.py +0 -98
  380. agno/tools/memori.py +0 -339
  381. agno-2.2.13.dist-info/RECORD +0 -575
  382. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
  383. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/os/app.py CHANGED
@@ -1,21 +1,24 @@
1
1
  from contextlib import asynccontextmanager
2
2
  from functools import partial
3
3
  from os import getenv
4
- from typing import Any, Dict, List, Literal, Optional, Tuple, Union
4
+ from typing import Any, Dict, List, Literal, Optional, Union
5
5
  from uuid import uuid4
6
6
 
7
7
  from fastapi import APIRouter, FastAPI, HTTPException
8
+ from fastapi.exceptions import RequestValidationError
8
9
  from fastapi.responses import JSONResponse
9
10
  from fastapi.routing import APIRoute
11
+ from httpx import HTTPStatusError
10
12
  from rich import box
11
13
  from rich.panel import Panel
12
14
  from starlette.requests import Request
13
15
 
14
- from agno.agent.agent import Agent
16
+ from agno.agent import Agent, RemoteAgent
15
17
  from agno.db.base import AsyncBaseDb, BaseDb
16
18
  from agno.knowledge.knowledge import Knowledge
17
19
  from agno.os.config import (
18
20
  AgentOSConfig,
21
+ AuthorizationConfig,
19
22
  DatabaseConfig,
20
23
  EvalsConfig,
21
24
  EvalsDomainConfig,
@@ -27,44 +30,77 @@ from agno.os.config import (
27
30
  MetricsDomainConfig,
28
31
  SessionConfig,
29
32
  SessionDomainConfig,
33
+ TracesConfig,
34
+ TracesDomainConfig,
30
35
  )
31
36
  from agno.os.interfaces.base import BaseInterface
32
37
  from agno.os.router import get_base_router, get_websocket_router
38
+ from agno.os.routers.agents import get_agent_router
39
+ from agno.os.routers.components import get_components_router
40
+ from agno.os.routers.database import get_database_router
33
41
  from agno.os.routers.evals import get_eval_router
34
42
  from agno.os.routers.health import get_health_router
35
43
  from agno.os.routers.home import get_home_router
36
44
  from agno.os.routers.knowledge import get_knowledge_router
37
45
  from agno.os.routers.memory import get_memory_router
38
46
  from agno.os.routers.metrics import get_metrics_router
47
+ from agno.os.routers.registry import get_registry_router
39
48
  from agno.os.routers.session import get_session_router
49
+ from agno.os.routers.teams import get_team_router
50
+ from agno.os.routers.traces import get_traces_router
51
+ from agno.os.routers.workflows import get_workflow_router
40
52
  from agno.os.settings import AgnoAPISettings
41
53
  from agno.os.utils import (
42
54
  collect_mcp_tools_from_team,
43
55
  collect_mcp_tools_from_workflow,
44
56
  find_conflicting_routes,
45
57
  load_yaml_config,
58
+ resolve_origins,
59
+ setup_tracing_for_os,
46
60
  update_cors_middleware,
47
61
  )
48
- from agno.team.team import Team
49
- from agno.utils.log import log_debug, log_error, log_warning
62
+ from agno.registry import Registry
63
+ from agno.remote.base import RemoteDb, RemoteKnowledge
64
+ from agno.team import RemoteTeam, Team
65
+ from agno.utils.log import log_debug, log_error, log_info, log_warning
50
66
  from agno.utils.string import generate_id, generate_id_from_name
51
- from agno.workflow.workflow import Workflow
67
+ from agno.workflow import RemoteWorkflow, Workflow
52
68
 
53
69
 
54
70
  @asynccontextmanager
55
71
  async def mcp_lifespan(_, mcp_tools):
56
72
  """Manage MCP connection lifecycle inside a FastAPI app"""
57
- # Startup logic: connect to all contextual MCP servers
58
73
  for tool in mcp_tools:
59
74
  await tool.connect()
60
75
 
61
76
  yield
62
77
 
63
- # Shutdown logic: Close all contextual MCP connections
64
78
  for tool in mcp_tools:
65
79
  await tool.close()
66
80
 
67
81
 
82
+ @asynccontextmanager
83
+ async def http_client_lifespan(_):
84
+ """Manage httpx client lifecycle for proper connection pool cleanup."""
85
+ from agno.utils.http import aclose_default_clients
86
+
87
+ yield
88
+
89
+ await aclose_default_clients()
90
+
91
+
92
+ @asynccontextmanager
93
+ async def db_lifespan(app: FastAPI, agent_os: "AgentOS"):
94
+ """Initializes databases in the event loop and closes them on shutdown."""
95
+ if agent_os.auto_provision_dbs:
96
+ agent_os._initialize_sync_databases()
97
+ await agent_os._initialize_async_databases()
98
+
99
+ yield
100
+
101
+ await agent_os._close_databases()
102
+
103
+
68
104
  def _combine_app_lifespans(lifespans: list) -> Any:
69
105
  """Combine multiple FastAPI app lifespan context managers into one."""
70
106
  if len(lifespans) == 1:
@@ -96,24 +132,27 @@ class AgentOS:
96
132
  name: Optional[str] = None,
97
133
  description: Optional[str] = None,
98
134
  version: Optional[str] = None,
99
- agents: Optional[List[Agent]] = None,
100
- teams: Optional[List[Team]] = None,
101
- workflows: Optional[List[Workflow]] = None,
135
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
136
+ agents: Optional[List[Union[Agent, RemoteAgent]]] = None,
137
+ teams: Optional[List[Union[Team, RemoteTeam]]] = None,
138
+ workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = None,
102
139
  knowledge: Optional[List[Knowledge]] = None,
103
140
  interfaces: Optional[List[BaseInterface]] = None,
104
141
  a2a_interface: bool = False,
142
+ authorization: bool = False,
143
+ authorization_config: Optional[AuthorizationConfig] = None,
144
+ cors_allowed_origins: Optional[List[str]] = None,
105
145
  config: Optional[Union[str, AgentOSConfig]] = None,
106
146
  settings: Optional[AgnoAPISettings] = None,
107
147
  lifespan: Optional[Any] = None,
108
148
  enable_mcp_server: bool = False,
109
149
  base_app: Optional[FastAPI] = None,
110
150
  on_route_conflict: Literal["preserve_agentos", "preserve_base_app", "error"] = "preserve_agentos",
111
- telemetry: bool = True,
151
+ tracing: bool = False,
112
152
  auto_provision_dbs: bool = True,
113
- os_id: Optional[str] = None, # Deprecated
114
- enable_mcp: bool = False, # Deprecated
115
- fastapi_app: Optional[FastAPI] = None, # Deprecated
116
- replace_routes: Optional[bool] = None, # Deprecated
153
+ run_hooks_in_background: bool = False,
154
+ telemetry: bool = True,
155
+ registry: Optional[Registry] = None,
117
156
  ):
118
157
  """Initialize AgentOS.
119
158
 
@@ -122,6 +161,7 @@ class AgentOS:
122
161
  name: Name of the AgentOS instance
123
162
  description: Description of the AgentOS instance
124
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.
125
165
  agents: List of agents to include in the OS
126
166
  teams: List of teams to include in the OS
127
167
  workflows: List of workflows to include in the OS
@@ -134,17 +174,24 @@ class AgentOS:
134
174
  enable_mcp_server: Whether to enable MCP (Model Context Protocol)
135
175
  base_app: Optional base FastAPI app to use for the AgentOS. All routes and middleware will be added to this app.
136
176
  on_route_conflict: What to do when a route conflict is detected in case a custom base_app is provided.
177
+ auto_provision_dbs: Whether to automatically provision databases
178
+ authorization: Whether to enable authorization
179
+ authorization_config: Configuration for the authorization middleware
180
+ cors_allowed_origins: List of allowed CORS origins (will be merged with default Agno domains)
181
+ tracing: If True, enables OpenTelemetry tracing for all agents and teams in the OS
182
+ run_hooks_in_background: If True, run agent/team pre/post hooks as FastAPI background tasks (non-blocking)
137
183
  telemetry: Whether to enable telemetry
184
+ registry: Optional registry to use for the AgentOS
138
185
 
139
186
  """
140
- if not agents and not workflows and not teams and not knowledge:
141
- 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.")
142
189
 
143
190
  self.config = load_yaml_config(config) if isinstance(config, str) else config
144
191
 
145
- self.agents: Optional[List[Agent]] = agents
146
- self.workflows: Optional[List[Workflow]] = workflows
147
- self.teams: Optional[List[Team]] = teams
192
+ self.agents: Optional[List[Union[Agent, RemoteAgent]]] = agents
193
+ self.workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = workflows
194
+ self.teams: Optional[List[Union[Team, RemoteTeam]]] = teams
148
195
  self.interfaces = interfaces or []
149
196
  self.a2a_interface = a2a_interface
150
197
  self.knowledge = knowledge
@@ -156,13 +203,6 @@ class AgentOS:
156
203
  self.base_app: Optional[FastAPI] = base_app
157
204
  self._app_set = True
158
205
  self.on_route_conflict = on_route_conflict
159
- elif fastapi_app:
160
- self.base_app = fastapi_app
161
- self._app_set = True
162
- if replace_routes is not None:
163
- self.on_route_conflict = "preserve_agentos" if replace_routes else "preserve_base_app"
164
- else:
165
- self.on_route_conflict = on_route_conflict
166
206
  else:
167
207
  self.base_app = None
168
208
  self._app_set = False
@@ -172,18 +212,32 @@ class AgentOS:
172
212
 
173
213
  self.name = name
174
214
 
175
- self.id = id or os_id
215
+ self.id = id
176
216
  if not self.id:
177
217
  self.id = generate_id(self.name) if self.name else str(uuid4())
178
218
 
179
219
  self.version = version
180
220
  self.description = description
221
+ self.db = db
181
222
 
182
223
  self.telemetry = telemetry
224
+ self.tracing = tracing
183
225
 
184
- self.enable_mcp_server = enable_mcp or enable_mcp_server
226
+ self.enable_mcp_server = enable_mcp_server
185
227
  self.lifespan = lifespan
186
228
 
229
+ self.registry = registry
230
+
231
+ # RBAC
232
+ self.authorization = authorization
233
+ self.authorization_config = authorization_config
234
+
235
+ # CORS configuration - merge user-provided origins with defaults from settings
236
+ self.cors_allowed_origins = resolve_origins(cors_allowed_origins, self.settings.cors_origin_list)
237
+
238
+ # If True, run agent/team hooks as FastAPI background tasks
239
+ self.run_hooks_in_background = run_hooks_in_background
240
+
187
241
  # List of all MCP tools used inside the AgentOS
188
242
  self.mcp_tools: List[Any] = []
189
243
  self._mcp_app: Optional[Any] = None
@@ -192,6 +246,12 @@ class AgentOS:
192
246
  self._initialize_teams()
193
247
  self._initialize_workflows()
194
248
 
249
+ # Check for duplicate IDs
250
+ self._raise_if_duplicate_ids()
251
+
252
+ if self.tracing:
253
+ self._setup_tracing()
254
+
195
255
  if self.telemetry:
196
256
  from agno.api.os import OSLaunch, log_os_telemetry
197
257
 
@@ -230,6 +290,9 @@ class AgentOS:
230
290
  self._initialize_agents()
231
291
  self._initialize_teams()
232
292
  self._initialize_workflows()
293
+
294
+ # Check for duplicate IDs
295
+ self._raise_if_duplicate_ids()
233
296
  self._auto_discover_databases()
234
297
  self._auto_discover_knowledge_instances()
235
298
 
@@ -243,12 +306,21 @@ class AgentOS:
243
306
  def _reprovision_routers(self, app: FastAPI) -> None:
244
307
  """Re-provision all routes for the AgentOS."""
245
308
  updated_routers = [
309
+ get_home_router(self),
246
310
  get_session_router(dbs=self.dbs),
247
- get_metrics_router(dbs=self.dbs),
248
- get_knowledge_router(knowledge_instances=self.knowledge_instances),
249
311
  get_memory_router(dbs=self.dbs),
250
312
  get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
313
+ get_metrics_router(dbs=self.dbs),
314
+ get_knowledge_router(knowledge_instances=self.knowledge_instances),
315
+ get_traces_router(dbs=self.dbs),
316
+ get_database_router(self, settings=self.settings),
251
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))
252
324
 
253
325
  # Clear all previously existing routes
254
326
  app.router.routes = [
@@ -278,6 +350,9 @@ class AgentOS:
278
350
 
279
351
  self._add_router(app, get_health_router(health_endpoint="/health"))
280
352
  self._add_router(app, get_base_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))
355
+ self._add_router(app, get_workflow_router(self, settings=self.settings))
281
356
  self._add_router(app, get_websocket_router(self, settings=self.settings))
282
357
 
283
358
  # Add A2A interface if relevant
@@ -294,6 +369,31 @@ class AgentOS:
294
369
  self.interfaces.append(a2a_interface)
295
370
  self._add_router(app, a2a_interface.get_router())
296
371
 
372
+ def _raise_if_duplicate_ids(self) -> None:
373
+ """Check for duplicate IDs within each entity type.
374
+
375
+ Raises:
376
+ ValueError: If duplicate IDs are found within the same entity type
377
+ """
378
+ duplicate_ids: List[str] = []
379
+
380
+ for entities in [self.agents, self.teams, self.workflows]:
381
+ if not entities:
382
+ continue
383
+ seen_ids: set[str] = set()
384
+ for entity in entities:
385
+ entity_id = entity.id
386
+ if entity_id is None:
387
+ continue
388
+ if entity_id in seen_ids:
389
+ if entity_id not in duplicate_ids:
390
+ duplicate_ids.append(entity_id)
391
+ else:
392
+ seen_ids.add(entity_id)
393
+
394
+ if duplicate_ids:
395
+ raise ValueError(f"Duplicate IDs found in AgentOS: {', '.join(repr(id_) for id_ in duplicate_ids)}")
396
+
297
397
  def _make_app(self, lifespan: Optional[Any] = None) -> FastAPI:
298
398
  # Adjust the FastAPI app lifespan to handle MCP connections if relevant
299
399
  app_lifespan = lifespan
@@ -327,28 +427,43 @@ class AgentOS:
327
427
  """Initialize and configure all agents for AgentOS usage."""
328
428
  if not self.agents:
329
429
  return
330
-
331
430
  for agent in self.agents:
431
+ if isinstance(agent, RemoteAgent):
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
332
436
  # Track all MCP tools to later handle their connection
333
437
  if agent.tools:
334
438
  for tool in agent.tools:
335
- # Checking if the tool is a MCPTools or MultiMCPTools instance
336
- type_name = type(tool).__name__
337
- if type_name in ("MCPTools", "MultiMCPTools"):
338
- if tool not in self.mcp_tools:
339
- self.mcp_tools.append(tool)
439
+ # Checking if the tool is an instance of MCPTools, MultiMCPTools, or a subclass of those
440
+ if hasattr(type(tool), "__mro__"):
441
+ mro_names = {cls.__name__ for cls in type(tool).__mro__}
442
+ if mro_names & {"MCPTools", "MultiMCPTools"}:
443
+ if tool not in self.mcp_tools:
444
+ self.mcp_tools.append(tool)
340
445
 
341
446
  agent.initialize_agent()
342
447
 
343
448
  # Required for the built-in routes to work
344
449
  agent.store_events = True
345
450
 
451
+ # Propagate run_hooks_in_background setting from AgentOS to agents
452
+ agent._run_hooks_in_background = self.run_hooks_in_background
453
+
346
454
  def _initialize_teams(self) -> None:
347
455
  """Initialize and configure all teams for AgentOS usage."""
348
456
  if not self.teams:
349
457
  return
350
458
 
351
459
  for team in self.teams:
460
+ if isinstance(team, RemoteTeam):
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
+
352
467
  # Track all MCP tools recursively
353
468
  collect_mcp_tools_from_team(team, self.mcp_tools)
354
469
 
@@ -364,21 +479,72 @@ class AgentOS:
364
479
  # Required for the built-in routes to work
365
480
  team.store_events = True
366
481
 
482
+ # Propagate run_hooks_in_background setting to team and all nested members
483
+ team.propagate_run_hooks_in_background(self.run_hooks_in_background)
484
+
367
485
  def _initialize_workflows(self) -> None:
368
486
  """Initialize and configure all workflows for AgentOS usage."""
369
487
  if not self.workflows:
370
488
  return
371
489
 
372
- if self.workflows:
373
- for workflow in self.workflows:
374
- # Track MCP tools recursively in workflow members
375
- 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
496
+
497
+ # Track MCP tools recursively in workflow members
498
+ collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
499
+
500
+ if not workflow.id:
501
+ workflow.id = generate_id_from_name(workflow.name)
502
+
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)
508
+
509
+ def _setup_tracing(self) -> None:
510
+ """Set up OpenTelemetry tracing for this AgentOS.
376
511
 
377
- if not workflow.id:
378
- workflow.id = generate_id_from_name(workflow.name)
512
+ Uses the AgentOS db if provided, otherwise falls back to the first available
513
+ database from agents/teams/workflows.
514
+ """
515
+ # Use AgentOS db if explicitly provided
516
+ if self.db is not None:
517
+ setup_tracing_for_os(db=self.db)
518
+ return
519
+
520
+ # Fall back to finding the first available database
521
+ db: Optional[Union[BaseDb, AsyncBaseDb, RemoteDb]] = None
522
+
523
+ for agent in self.agents or []:
524
+ if agent.db:
525
+ db = agent.db
526
+ break
527
+
528
+ if db is None:
529
+ for team in self.teams or []:
530
+ if team.db:
531
+ db = team.db
532
+ break
533
+
534
+ if db is None:
535
+ for workflow in self.workflows or []:
536
+ if workflow.db:
537
+ db = workflow.db
538
+ break
539
+
540
+ if db is None:
541
+ log_warning(
542
+ "tracing=True but no database found. "
543
+ "Provide 'db' parameter to AgentOS or to at least one agent/team/workflow."
544
+ )
545
+ return
379
546
 
380
- # Required for the built-in routes to work
381
- workflow.store_events = True
547
+ setup_tracing_for_os(db=db)
382
548
 
383
549
  def get_app(self) -> FastAPI:
384
550
  if self.base_app:
@@ -411,41 +577,42 @@ class AgentOS:
411
577
  if self.enable_mcp_server and self._mcp_app:
412
578
  lifespans.append(self._mcp_app.lifespan)
413
579
 
580
+ # The async database lifespan
581
+ lifespans.append(partial(db_lifespan, agent_os=self))
582
+
583
+ # The httpx client cleanup lifespan (should be last to close after other lifespans)
584
+ lifespans.append(http_client_lifespan)
585
+
414
586
  # Combine lifespans and set them in the app
415
587
  if lifespans:
416
588
  fastapi_app.router.lifespan_context = _combine_app_lifespans(lifespans)
417
589
 
418
590
  else:
419
- if self.enable_mcp_server:
420
- from contextlib import asynccontextmanager
591
+ lifespans = []
421
592
 
422
- from agno.os.mcp import get_mcp_server
593
+ # User provided lifespan
594
+ if self.lifespan:
595
+ lifespans.append(self._add_agent_os_to_lifespan_function(self.lifespan))
423
596
 
424
- self._mcp_app = get_mcp_server(self)
597
+ # MCP tools lifespan
598
+ if self.mcp_tools:
599
+ lifespans.append(partial(mcp_lifespan, mcp_tools=self.mcp_tools))
425
600
 
426
- final_lifespan = self._mcp_app.lifespan # type: ignore
427
- if self.lifespan is not None:
428
- # Wrap the user lifespan with agent_os parameter
429
- wrapped_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
601
+ # MCP server lifespan
602
+ if self.enable_mcp_server:
603
+ from agno.os.mcp import get_mcp_server
430
604
 
431
- # Combine both lifespans
432
- @asynccontextmanager
433
- async def combined_lifespan(app: FastAPI):
434
- # Run both lifespans
435
- async with wrapped_lifespan(app): # type: ignore
436
- async with self._mcp_app.lifespan(app): # type: ignore
437
- yield
605
+ self._mcp_app = get_mcp_server(self)
606
+ lifespans.append(self._mcp_app.lifespan)
438
607
 
439
- final_lifespan = combined_lifespan # type: ignore
608
+ # Async database initialization lifespan
609
+ lifespans.append(partial(db_lifespan, agent_os=self)) # type: ignore
440
610
 
441
- fastapi_app = self._make_app(lifespan=final_lifespan)
442
- else:
443
- # Wrap the user lifespan with agent_os parameter
444
- wrapped_user_lifespan = None
445
- if self.lifespan is not None:
446
- wrapped_user_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
611
+ # The httpx client cleanup lifespan (should be last to close after other lifespans)
612
+ lifespans.append(http_client_lifespan)
447
613
 
448
- fastapi_app = self._make_app(lifespan=wrapped_user_lifespan)
614
+ final_lifespan = _combine_app_lifespans(lifespans) if lifespans else None
615
+ fastapi_app = self._make_app(lifespan=final_lifespan)
449
616
 
450
617
  self._add_built_in_routes(app=fastapi_app)
451
618
 
@@ -458,7 +625,15 @@ class AgentOS:
458
625
  get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
459
626
  get_metrics_router(dbs=self.dbs),
460
627
  get_knowledge_router(knowledge_instances=self.knowledge_instances),
628
+ get_traces_router(dbs=self.dbs),
629
+ get_database_router(self, settings=self.settings),
461
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))
462
637
 
463
638
  for router in routers:
464
639
  self._add_router(fastapi_app, router)
@@ -469,6 +644,14 @@ class AgentOS:
469
644
 
470
645
  if not self._app_set:
471
646
 
647
+ @fastapi_app.exception_handler(RequestValidationError)
648
+ async def validation_exception_handler(_: Request, exc: RequestValidationError) -> JSONResponse:
649
+ log_error(f"Validation error (422): {exc.errors()}")
650
+ return JSONResponse(
651
+ status_code=422,
652
+ content={"detail": exc.errors()},
653
+ )
654
+
472
655
  @fastapi_app.exception_handler(HTTPException)
473
656
  async def http_exception_handler(_, exc: HTTPException) -> JSONResponse:
474
657
  log_error(f"HTTP exception: {exc.status_code} {exc.detail}")
@@ -477,6 +660,16 @@ class AgentOS:
477
660
  content={"detail": str(exc.detail)},
478
661
  )
479
662
 
663
+ @fastapi_app.exception_handler(HTTPStatusError)
664
+ async def http_status_error_handler(_: Request, exc: HTTPStatusError) -> JSONResponse:
665
+ status_code = exc.response.status_code
666
+ detail = exc.response.text
667
+ log_error(f"Downstream server returned HTTP status error: {status_code} {detail}")
668
+ return JSONResponse(
669
+ status_code=status_code,
670
+ content={"detail": detail},
671
+ )
672
+
480
673
  @fastapi_app.exception_handler(Exception)
481
674
  async def general_exception_handler(_: Request, exc: Exception) -> JSONResponse:
482
675
  import traceback
@@ -489,10 +682,70 @@ class AgentOS:
489
682
  )
490
683
 
491
684
  # Update CORS middleware
492
- update_cors_middleware(fastapi_app, self.settings.cors_origin_list) # type: ignore
685
+ update_cors_middleware(fastapi_app, self.cors_allowed_origins) # type: ignore
686
+
687
+ # Set agent_os_id and cors_allowed_origins on app state
688
+ # This allows middleware (like JWT) to access these values
689
+ fastapi_app.state.agent_os_id = self.id
690
+ fastapi_app.state.cors_allowed_origins = self.cors_allowed_origins
691
+
692
+ # Add JWT middleware if authorization is enabled
693
+ if self.authorization:
694
+ # Set authorization_enabled flag on settings so security key validation is skipped
695
+ self.settings.authorization_enabled = True
696
+
697
+ jwt_configured = bool(getenv("JWT_VERIFICATION_KEY") or getenv("JWT_JWKS_FILE"))
698
+ security_key_set = bool(self.settings.os_security_key)
699
+ if jwt_configured and security_key_set:
700
+ log_warning(
701
+ "Both JWT configuration (JWT_VERIFICATION_KEY or JWT_JWKS_FILE) and OS_SECURITY_KEY are set. "
702
+ "With authorization=True, only JWT authorization will be used. "
703
+ "Consider removing OS_SECURITY_KEY from your environment."
704
+ )
705
+
706
+ self._add_jwt_middleware(fastapi_app)
707
+
708
+ # Add trailing slash normalization middleware
709
+ from agno.os.middleware.trailing_slash import TrailingSlashMiddleware
710
+
711
+ fastapi_app.add_middleware(TrailingSlashMiddleware)
493
712
 
494
713
  return fastapi_app
495
714
 
715
+ def _add_jwt_middleware(self, fastapi_app: FastAPI) -> None:
716
+ from agno.os.middleware.jwt import JWTMiddleware, JWTValidator
717
+
718
+ verify_audience = False
719
+ jwks_file = None
720
+ verification_keys = None
721
+ algorithm = "RS256"
722
+
723
+ if self.authorization_config:
724
+ algorithm = self.authorization_config.algorithm or "RS256"
725
+ verification_keys = self.authorization_config.verification_keys
726
+ jwks_file = self.authorization_config.jwks_file
727
+ verify_audience = self.authorization_config.verify_audience or False
728
+
729
+ log_info(f"Adding JWT middleware for authorization (algorithm: {algorithm})")
730
+
731
+ # Create validator and store on app.state for WebSocket access
732
+ jwt_validator = JWTValidator(
733
+ verification_keys=verification_keys,
734
+ jwks_file=jwks_file,
735
+ algorithm=algorithm,
736
+ )
737
+ fastapi_app.state.jwt_validator = jwt_validator
738
+
739
+ # Add middleware to stack
740
+ fastapi_app.add_middleware(
741
+ JWTMiddleware,
742
+ verification_keys=verification_keys,
743
+ jwks_file=jwks_file,
744
+ algorithm=algorithm,
745
+ authorization=self.authorization,
746
+ verify_audience=verify_audience,
747
+ )
748
+
496
749
  def get_routes(self) -> List[Any]:
497
750
  """Retrieve all routes from the FastAPI app.
498
751
 
@@ -561,32 +814,43 @@ class AgentOS:
561
814
 
562
815
  def _get_telemetry_data(self) -> Dict[str, Any]:
563
816
  """Get the telemetry data for the OS"""
817
+ agent_ids = []
818
+ team_ids = []
819
+ workflow_ids = []
820
+ for agent in self.agents or []:
821
+ agent_ids.append(agent.id)
822
+ for team in self.teams or []:
823
+ team_ids.append(team.id)
824
+ for workflow in self.workflows or []:
825
+ workflow_ids.append(workflow.id)
564
826
  return {
565
- "agents": [agent.id for agent in self.agents] if self.agents else None,
566
- "teams": [team.id for team in self.teams] if self.teams else None,
567
- "workflows": [workflow.id for workflow in self.workflows] if self.workflows else None,
827
+ "agents": agent_ids,
828
+ "teams": team_ids,
829
+ "workflows": workflow_ids,
568
830
  "interfaces": [interface.type for interface in self.interfaces] if self.interfaces else None,
569
831
  }
570
832
 
571
833
  def _auto_discover_databases(self) -> None:
572
834
  """Auto-discover and initialize the databases used by all contextual agents, teams and workflows."""
573
835
 
574
- dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb]]] = {}
836
+ dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb, RemoteDb]]] = {}
575
837
  knowledge_dbs: Dict[
576
- str, List[Union[BaseDb, AsyncBaseDb]]
838
+ str, List[Union[BaseDb, AsyncBaseDb, RemoteDb]]
577
839
  ] = {} # Track databases specifically used for knowledge
578
840
 
579
841
  for agent in self.agents or []:
580
842
  if agent.db:
581
843
  self._register_db_with_validation(dbs, agent.db)
582
- if agent.knowledge and agent.knowledge.contents_db:
583
- 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)
584
847
 
585
848
  for team in self.teams or []:
586
849
  if team.db:
587
850
  self._register_db_with_validation(dbs, team.db)
588
- if team.knowledge and team.knowledge.contents_db:
589
- 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)
590
854
 
591
855
  for workflow in self.workflows or []:
592
856
  if workflow.db:
@@ -602,39 +866,21 @@ class AgentOS:
602
866
  elif interface.team and interface.team.db:
603
867
  self._register_db_with_validation(dbs, interface.team.db)
604
868
 
869
+ # Register AgentOS db if provided
870
+ if self.db is not None:
871
+ self._register_db_with_validation(dbs, self.db)
872
+
605
873
  self.dbs = dbs
606
874
  self.knowledge_dbs = knowledge_dbs
607
875
 
608
- # Initialize/scaffold all discovered databases
876
+ # Initialize all discovered databases
609
877
  if self.auto_provision_dbs:
610
- import asyncio
611
- import concurrent.futures
878
+ self._pending_async_db_init = True
612
879
 
613
- try:
614
- # If we're already in an event loop, run in a separate thread
615
- asyncio.get_running_loop()
616
-
617
- def run_in_new_loop():
618
- new_loop = asyncio.new_event_loop()
619
- asyncio.set_event_loop(new_loop)
620
- try:
621
- return new_loop.run_until_complete(self._initialize_databases())
622
- finally:
623
- new_loop.close()
624
-
625
- with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
626
- future = executor.submit(run_in_new_loop)
627
- future.result() # Wait for completion
628
-
629
- except RuntimeError:
630
- # No event loop running, use asyncio.run
631
- asyncio.run(self._initialize_databases())
632
-
633
- async def _initialize_databases(self) -> None:
634
- """Initialize all discovered databases and create all Agno tables that don't exist yet."""
880
+ def _initialize_sync_databases(self) -> None:
881
+ """Initialize sync databases."""
635
882
  from itertools import chain
636
883
 
637
- # Collect all database instances and remove duplicates by identity
638
884
  unique_dbs = list(
639
885
  {
640
886
  id(db): db
@@ -644,37 +890,65 @@ class AgentOS:
644
890
  }.values()
645
891
  )
646
892
 
647
- # Separate sync and async databases
648
- sync_dbs: List[Tuple[str, BaseDb]] = []
649
- async_dbs: List[Tuple[str, AsyncBaseDb]] = []
650
-
651
893
  for db in unique_dbs:
652
- target = async_dbs if isinstance(db, AsyncBaseDb) else sync_dbs
653
- target.append((db.id, db)) # type: ignore
894
+ if isinstance(db, AsyncBaseDb):
895
+ continue # Skip async dbs
654
896
 
655
- # Initialize sync databases
656
- for db_id, db in sync_dbs:
657
897
  try:
658
- if hasattr(db, "_create_all_tables") and callable(getattr(db, "_create_all_tables")):
898
+ if hasattr(db, "_create_all_tables") and callable(db._create_all_tables):
659
899
  db._create_all_tables()
660
- else:
661
- log_debug(f"No table initialization needed for {db.__class__.__name__}")
662
-
663
900
  except Exception as e:
664
- log_warning(f"Failed to initialize {db.__class__.__name__} (id: {db_id}): {e}")
901
+ log_warning(f"Failed to initialize {db.__class__.__name__} (id: {db.id}): {e}")
665
902
 
666
- # Initialize async databases
667
- for db_id, db in async_dbs:
668
- try:
669
- log_debug(f"Initializing async {db.__class__.__name__} (id: {db_id})")
903
+ async def _initialize_async_databases(self) -> None:
904
+ """Initialize async databases."""
905
+
906
+ from itertools import chain
907
+
908
+ unique_dbs = list(
909
+ {
910
+ id(db): db
911
+ for db in chain(
912
+ chain.from_iterable(self.dbs.values()), chain.from_iterable(self.knowledge_dbs.values())
913
+ )
914
+ }.values()
915
+ )
916
+
917
+ for db in unique_dbs:
918
+ if not isinstance(db, AsyncBaseDb):
919
+ continue # Skip sync dbs
670
920
 
671
- if hasattr(db, "_create_all_tables") and callable(getattr(db, "_create_all_tables")):
921
+ try:
922
+ if hasattr(db, "_create_all_tables") and callable(db._create_all_tables):
672
923
  await db._create_all_tables()
673
- else:
674
- log_debug(f"No table initialization needed for async {db.__class__.__name__}")
924
+ except Exception as e:
925
+ log_warning(f"Failed to initialize async {db.__class__.__name__} (id: {db.id}): {e}")
926
+
927
+ async def _close_databases(self) -> None:
928
+ """Close all database connections and release connection pools."""
929
+ from itertools import chain
930
+
931
+ if not hasattr(self, "dbs") or not hasattr(self, "knowledge_dbs"):
932
+ return
933
+
934
+ unique_dbs = list(
935
+ {
936
+ id(db): db
937
+ for db in chain(
938
+ chain.from_iterable(self.dbs.values()), chain.from_iterable(self.knowledge_dbs.values())
939
+ )
940
+ }.values()
941
+ )
675
942
 
943
+ for db in unique_dbs:
944
+ try:
945
+ if hasattr(db, "close") and callable(db.close):
946
+ if isinstance(db, AsyncBaseDb):
947
+ await db.close()
948
+ else:
949
+ db.close()
676
950
  except Exception as e:
677
- log_warning(f"Failed to initialize async database {db.__class__.__name__} (id: {db_id}): {e}")
951
+ log_warning(f"Failed to close {db.__class__.__name__} (id: {db.id}): {e}")
678
952
 
679
953
  def _get_db_table_names(self, db: BaseDb) -> Dict[str, str]:
680
954
  """Get the table names for a database"""
@@ -689,7 +963,9 @@ class AgentOS:
689
963
  return {k: v for k, v in table_names.items() if v is not None}
690
964
 
691
965
  def _register_db_with_validation(
692
- self, registered_dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb]]], db: Union[BaseDb, AsyncBaseDb]
966
+ self,
967
+ registered_dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb, RemoteDb]]],
968
+ db: Union[BaseDb, AsyncBaseDb, RemoteDb],
693
969
  ) -> None:
694
970
  """Register a database in the contextual OS after validating it is not conflicting with registered databases"""
695
971
  if db.id in registered_dbs:
@@ -699,18 +975,21 @@ class AgentOS:
699
975
 
700
976
  def _auto_discover_knowledge_instances(self) -> None:
701
977
  """Auto-discover the knowledge instances used by all contextual agents, teams and workflows."""
702
- seen_ids = set()
703
- knowledge_instances: List[Knowledge] = []
978
+ seen_ids: set[str] = set()
979
+ knowledge_instances: List[Union[Knowledge, RemoteKnowledge]] = []
704
980
 
705
- def _add_knowledge_if_not_duplicate(knowledge: "Knowledge") -> None:
981
+ def _add_knowledge_if_not_duplicate(knowledge: Any) -> None:
706
982
  """Add knowledge instance if it's not already in the list (by object identity or db_id)."""
707
- # Use database ID if available, otherwise use object ID as fallback
708
- 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:
709
986
  return
710
- if knowledge.contents_db.id in seen_ids:
987
+ if contents_db.id in seen_ids:
711
988
  return
712
- seen_ids.add(knowledge.contents_db.id)
713
- 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)
714
993
 
715
994
  for agent in self.agents or []:
716
995
  if agent.knowledge:
@@ -832,6 +1111,36 @@ class AgentOS:
832
1111
 
833
1112
  return evals_config
834
1113
 
1114
+ def _get_traces_config(self) -> TracesConfig:
1115
+ traces_config = self.config.traces if self.config and self.config.traces else TracesConfig()
1116
+
1117
+ if traces_config.dbs is None:
1118
+ traces_config.dbs = []
1119
+
1120
+ dbs_with_specific_config = [db.db_id for db in traces_config.dbs]
1121
+
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:
1125
+ traces_config.dbs.append(
1126
+ DatabaseConfig(
1127
+ db_id=self.db.id,
1128
+ domain_config=TracesDomainConfig(display_name=self.db.id),
1129
+ )
1130
+ )
1131
+ else:
1132
+ # Fall back to all discovered databases
1133
+ for db_id in self.dbs.keys():
1134
+ if db_id not in dbs_with_specific_config:
1135
+ traces_config.dbs.append(
1136
+ DatabaseConfig(
1137
+ db_id=db_id,
1138
+ domain_config=TracesDomainConfig(display_name=db_id),
1139
+ )
1140
+ )
1141
+
1142
+ return traces_config
1143
+
835
1144
  def serve(
836
1145
  self,
837
1146
  app: Union[str, FastAPI],
@@ -839,6 +1148,8 @@ class AgentOS:
839
1148
  host: str = "localhost",
840
1149
  port: int = 7777,
841
1150
  reload: bool = False,
1151
+ reload_includes: Optional[List[str]] = None,
1152
+ reload_excludes: Optional[List[str]] = None,
842
1153
  workers: Optional[int] = None,
843
1154
  access_log: bool = False,
844
1155
  **kwargs,
@@ -858,8 +1169,12 @@ class AgentOS:
858
1169
  Align.center(f"[bold cyan]{public_endpoint}[/bold cyan]"),
859
1170
  Align.center(f"\n\n[bold dark_orange]OS running on:[/bold dark_orange] http://{host}:{port}"),
860
1171
  ]
861
- if bool(self.settings.os_security_key):
862
- panel_group.append(Align.center("\n\n[bold chartreuse3]:lock: Security Enabled[/bold chartreuse3]"))
1172
+ if self.authorization:
1173
+ panel_group.append(
1174
+ Align.center("\n\n[bold chartreuse3]:lock: JWT Authorization Enabled[/bold chartreuse3]")
1175
+ )
1176
+ elif bool(self.settings.os_security_key):
1177
+ panel_group.append(Align.center("\n\n[bold chartreuse3]:lock: Security Key Enabled[/bold chartreuse3]"))
863
1178
 
864
1179
  console = Console()
865
1180
  console.print(
@@ -873,4 +1188,19 @@ class AgentOS:
873
1188
  )
874
1189
  )
875
1190
 
876
- uvicorn.run(app=app, host=host, port=port, reload=reload, workers=workers, access_log=access_log, **kwargs)
1191
+ # Adding *.yaml to reload_includes to reload the app when the yaml config file changes.
1192
+ if reload and reload_includes is not None:
1193
+ reload_includes = ["*.yaml", "*.yml"]
1194
+
1195
+ uvicorn.run(
1196
+ app=app,
1197
+ host=host,
1198
+ port=port,
1199
+ reload=reload,
1200
+ reload_includes=reload_includes,
1201
+ reload_excludes=reload_excludes,
1202
+ workers=workers,
1203
+ access_log=access_log,
1204
+ lifespan="on",
1205
+ **kwargs,
1206
+ )