agno 2.1.2__py3-none-any.whl → 2.3.13__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 (314) hide show
  1. agno/agent/agent.py +5540 -2273
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/compression/__init__.py +3 -0
  5. agno/compression/manager.py +247 -0
  6. agno/culture/__init__.py +3 -0
  7. agno/culture/manager.py +956 -0
  8. agno/db/async_postgres/__init__.py +3 -0
  9. agno/db/base.py +689 -6
  10. agno/db/dynamo/dynamo.py +933 -37
  11. agno/db/dynamo/schemas.py +174 -10
  12. agno/db/dynamo/utils.py +63 -4
  13. agno/db/firestore/firestore.py +831 -9
  14. agno/db/firestore/schemas.py +51 -0
  15. agno/db/firestore/utils.py +102 -4
  16. agno/db/gcs_json/gcs_json_db.py +660 -12
  17. agno/db/gcs_json/utils.py +60 -26
  18. agno/db/in_memory/in_memory_db.py +287 -14
  19. agno/db/in_memory/utils.py +60 -2
  20. agno/db/json/json_db.py +590 -14
  21. agno/db/json/utils.py +60 -26
  22. agno/db/migrations/manager.py +199 -0
  23. agno/db/migrations/v1_to_v2.py +43 -13
  24. agno/db/migrations/versions/__init__.py +0 -0
  25. agno/db/migrations/versions/v2_3_0.py +938 -0
  26. agno/db/mongo/__init__.py +15 -1
  27. agno/db/mongo/async_mongo.py +2760 -0
  28. agno/db/mongo/mongo.py +879 -11
  29. agno/db/mongo/schemas.py +42 -0
  30. agno/db/mongo/utils.py +80 -8
  31. agno/db/mysql/__init__.py +2 -1
  32. agno/db/mysql/async_mysql.py +2912 -0
  33. agno/db/mysql/mysql.py +946 -68
  34. agno/db/mysql/schemas.py +72 -10
  35. agno/db/mysql/utils.py +198 -7
  36. agno/db/postgres/__init__.py +2 -1
  37. agno/db/postgres/async_postgres.py +2579 -0
  38. agno/db/postgres/postgres.py +942 -57
  39. agno/db/postgres/schemas.py +81 -18
  40. agno/db/postgres/utils.py +164 -2
  41. agno/db/redis/redis.py +671 -7
  42. agno/db/redis/schemas.py +50 -0
  43. agno/db/redis/utils.py +65 -7
  44. agno/db/schemas/__init__.py +2 -1
  45. agno/db/schemas/culture.py +120 -0
  46. agno/db/schemas/evals.py +1 -0
  47. agno/db/schemas/memory.py +17 -2
  48. agno/db/singlestore/schemas.py +63 -0
  49. agno/db/singlestore/singlestore.py +949 -83
  50. agno/db/singlestore/utils.py +60 -2
  51. agno/db/sqlite/__init__.py +2 -1
  52. agno/db/sqlite/async_sqlite.py +2911 -0
  53. agno/db/sqlite/schemas.py +62 -0
  54. agno/db/sqlite/sqlite.py +965 -46
  55. agno/db/sqlite/utils.py +169 -8
  56. agno/db/surrealdb/__init__.py +3 -0
  57. agno/db/surrealdb/metrics.py +292 -0
  58. agno/db/surrealdb/models.py +334 -0
  59. agno/db/surrealdb/queries.py +71 -0
  60. agno/db/surrealdb/surrealdb.py +1908 -0
  61. agno/db/surrealdb/utils.py +147 -0
  62. agno/db/utils.py +2 -0
  63. agno/eval/__init__.py +10 -0
  64. agno/eval/accuracy.py +75 -55
  65. agno/eval/agent_as_judge.py +861 -0
  66. agno/eval/base.py +29 -0
  67. agno/eval/performance.py +16 -7
  68. agno/eval/reliability.py +28 -16
  69. agno/eval/utils.py +35 -17
  70. agno/exceptions.py +27 -2
  71. agno/filters.py +354 -0
  72. agno/guardrails/prompt_injection.py +1 -0
  73. agno/hooks/__init__.py +3 -0
  74. agno/hooks/decorator.py +164 -0
  75. agno/integrations/discord/client.py +1 -1
  76. agno/knowledge/chunking/agentic.py +13 -10
  77. agno/knowledge/chunking/fixed.py +4 -1
  78. agno/knowledge/chunking/semantic.py +9 -4
  79. agno/knowledge/chunking/strategy.py +59 -15
  80. agno/knowledge/embedder/fastembed.py +1 -1
  81. agno/knowledge/embedder/nebius.py +1 -1
  82. agno/knowledge/embedder/ollama.py +8 -0
  83. agno/knowledge/embedder/openai.py +8 -8
  84. agno/knowledge/embedder/sentence_transformer.py +6 -2
  85. agno/knowledge/embedder/vllm.py +262 -0
  86. agno/knowledge/knowledge.py +1618 -318
  87. agno/knowledge/reader/base.py +6 -2
  88. agno/knowledge/reader/csv_reader.py +8 -10
  89. agno/knowledge/reader/docx_reader.py +5 -6
  90. agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
  91. agno/knowledge/reader/json_reader.py +5 -4
  92. agno/knowledge/reader/markdown_reader.py +8 -8
  93. agno/knowledge/reader/pdf_reader.py +17 -19
  94. agno/knowledge/reader/pptx_reader.py +101 -0
  95. agno/knowledge/reader/reader_factory.py +32 -3
  96. agno/knowledge/reader/s3_reader.py +3 -3
  97. agno/knowledge/reader/tavily_reader.py +193 -0
  98. agno/knowledge/reader/text_reader.py +22 -10
  99. agno/knowledge/reader/web_search_reader.py +1 -48
  100. agno/knowledge/reader/website_reader.py +10 -10
  101. agno/knowledge/reader/wikipedia_reader.py +33 -1
  102. agno/knowledge/types.py +1 -0
  103. agno/knowledge/utils.py +72 -7
  104. agno/media.py +22 -6
  105. agno/memory/__init__.py +14 -1
  106. agno/memory/manager.py +544 -83
  107. agno/memory/strategies/__init__.py +15 -0
  108. agno/memory/strategies/base.py +66 -0
  109. agno/memory/strategies/summarize.py +196 -0
  110. agno/memory/strategies/types.py +37 -0
  111. agno/models/aimlapi/aimlapi.py +17 -0
  112. agno/models/anthropic/claude.py +515 -40
  113. agno/models/aws/bedrock.py +102 -21
  114. agno/models/aws/claude.py +131 -274
  115. agno/models/azure/ai_foundry.py +41 -19
  116. agno/models/azure/openai_chat.py +39 -8
  117. agno/models/base.py +1249 -525
  118. agno/models/cerebras/cerebras.py +91 -21
  119. agno/models/cerebras/cerebras_openai.py +21 -2
  120. agno/models/cohere/chat.py +40 -6
  121. agno/models/cometapi/cometapi.py +18 -1
  122. agno/models/dashscope/dashscope.py +2 -3
  123. agno/models/deepinfra/deepinfra.py +18 -1
  124. agno/models/deepseek/deepseek.py +69 -3
  125. agno/models/fireworks/fireworks.py +18 -1
  126. agno/models/google/gemini.py +877 -80
  127. agno/models/google/utils.py +22 -0
  128. agno/models/groq/groq.py +51 -18
  129. agno/models/huggingface/huggingface.py +17 -6
  130. agno/models/ibm/watsonx.py +16 -6
  131. agno/models/internlm/internlm.py +18 -1
  132. agno/models/langdb/langdb.py +13 -1
  133. agno/models/litellm/chat.py +44 -9
  134. agno/models/litellm/litellm_openai.py +18 -1
  135. agno/models/message.py +28 -5
  136. agno/models/meta/llama.py +47 -14
  137. agno/models/meta/llama_openai.py +22 -17
  138. agno/models/mistral/mistral.py +8 -4
  139. agno/models/nebius/nebius.py +6 -7
  140. agno/models/nvidia/nvidia.py +20 -3
  141. agno/models/ollama/chat.py +24 -8
  142. agno/models/openai/chat.py +104 -29
  143. agno/models/openai/responses.py +101 -81
  144. agno/models/openrouter/openrouter.py +60 -3
  145. agno/models/perplexity/perplexity.py +17 -1
  146. agno/models/portkey/portkey.py +7 -6
  147. agno/models/requesty/requesty.py +24 -4
  148. agno/models/response.py +73 -2
  149. agno/models/sambanova/sambanova.py +20 -3
  150. agno/models/siliconflow/siliconflow.py +19 -2
  151. agno/models/together/together.py +20 -3
  152. agno/models/utils.py +254 -8
  153. agno/models/vercel/v0.py +20 -3
  154. agno/models/vertexai/__init__.py +0 -0
  155. agno/models/vertexai/claude.py +190 -0
  156. agno/models/vllm/vllm.py +19 -14
  157. agno/models/xai/xai.py +19 -2
  158. agno/os/app.py +549 -152
  159. agno/os/auth.py +190 -3
  160. agno/os/config.py +23 -0
  161. agno/os/interfaces/a2a/router.py +8 -11
  162. agno/os/interfaces/a2a/utils.py +1 -1
  163. agno/os/interfaces/agui/router.py +18 -3
  164. agno/os/interfaces/agui/utils.py +152 -39
  165. agno/os/interfaces/slack/router.py +55 -37
  166. agno/os/interfaces/slack/slack.py +9 -1
  167. agno/os/interfaces/whatsapp/router.py +0 -1
  168. agno/os/interfaces/whatsapp/security.py +3 -1
  169. agno/os/mcp.py +110 -52
  170. agno/os/middleware/__init__.py +2 -0
  171. agno/os/middleware/jwt.py +676 -112
  172. agno/os/router.py +40 -1478
  173. agno/os/routers/agents/__init__.py +3 -0
  174. agno/os/routers/agents/router.py +599 -0
  175. agno/os/routers/agents/schema.py +261 -0
  176. agno/os/routers/evals/evals.py +96 -39
  177. agno/os/routers/evals/schemas.py +65 -33
  178. agno/os/routers/evals/utils.py +80 -10
  179. agno/os/routers/health.py +10 -4
  180. agno/os/routers/knowledge/knowledge.py +196 -38
  181. agno/os/routers/knowledge/schemas.py +82 -22
  182. agno/os/routers/memory/memory.py +279 -52
  183. agno/os/routers/memory/schemas.py +46 -17
  184. agno/os/routers/metrics/metrics.py +20 -8
  185. agno/os/routers/metrics/schemas.py +16 -16
  186. agno/os/routers/session/session.py +462 -34
  187. agno/os/routers/teams/__init__.py +3 -0
  188. agno/os/routers/teams/router.py +512 -0
  189. agno/os/routers/teams/schema.py +257 -0
  190. agno/os/routers/traces/__init__.py +3 -0
  191. agno/os/routers/traces/schemas.py +414 -0
  192. agno/os/routers/traces/traces.py +499 -0
  193. agno/os/routers/workflows/__init__.py +3 -0
  194. agno/os/routers/workflows/router.py +624 -0
  195. agno/os/routers/workflows/schema.py +75 -0
  196. agno/os/schema.py +256 -693
  197. agno/os/scopes.py +469 -0
  198. agno/os/utils.py +514 -36
  199. agno/reasoning/anthropic.py +80 -0
  200. agno/reasoning/gemini.py +73 -0
  201. agno/reasoning/openai.py +5 -0
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +155 -32
  205. agno/run/base.py +55 -3
  206. agno/run/requirement.py +181 -0
  207. agno/run/team.py +125 -38
  208. agno/run/workflow.py +72 -18
  209. agno/session/agent.py +102 -89
  210. agno/session/summary.py +56 -15
  211. agno/session/team.py +164 -90
  212. agno/session/workflow.py +405 -40
  213. agno/table.py +10 -0
  214. agno/team/team.py +3974 -1903
  215. agno/tools/dalle.py +2 -4
  216. agno/tools/eleven_labs.py +23 -25
  217. agno/tools/exa.py +21 -16
  218. agno/tools/file.py +153 -23
  219. agno/tools/file_generation.py +16 -10
  220. agno/tools/firecrawl.py +15 -7
  221. agno/tools/function.py +193 -38
  222. agno/tools/gmail.py +238 -14
  223. agno/tools/google_drive.py +271 -0
  224. agno/tools/googlecalendar.py +36 -8
  225. agno/tools/googlesheets.py +20 -5
  226. agno/tools/jira.py +20 -0
  227. agno/tools/mcp/__init__.py +10 -0
  228. agno/tools/mcp/mcp.py +331 -0
  229. agno/tools/mcp/multi_mcp.py +347 -0
  230. agno/tools/mcp/params.py +24 -0
  231. agno/tools/mcp_toolbox.py +3 -3
  232. agno/tools/models/nebius.py +5 -5
  233. agno/tools/models_labs.py +20 -10
  234. agno/tools/nano_banana.py +151 -0
  235. agno/tools/notion.py +204 -0
  236. agno/tools/parallel.py +314 -0
  237. agno/tools/postgres.py +76 -36
  238. agno/tools/redshift.py +406 -0
  239. agno/tools/scrapegraph.py +1 -1
  240. agno/tools/shopify.py +1519 -0
  241. agno/tools/slack.py +18 -3
  242. agno/tools/spotify.py +919 -0
  243. agno/tools/tavily.py +146 -0
  244. agno/tools/toolkit.py +25 -0
  245. agno/tools/workflow.py +8 -1
  246. agno/tools/yfinance.py +12 -11
  247. agno/tracing/__init__.py +12 -0
  248. agno/tracing/exporter.py +157 -0
  249. agno/tracing/schemas.py +276 -0
  250. agno/tracing/setup.py +111 -0
  251. agno/utils/agent.py +938 -0
  252. agno/utils/cryptography.py +22 -0
  253. agno/utils/dttm.py +33 -0
  254. agno/utils/events.py +151 -3
  255. agno/utils/gemini.py +15 -5
  256. agno/utils/hooks.py +118 -4
  257. agno/utils/http.py +113 -2
  258. agno/utils/knowledge.py +12 -5
  259. agno/utils/log.py +1 -0
  260. agno/utils/mcp.py +92 -2
  261. agno/utils/media.py +187 -1
  262. agno/utils/merge_dict.py +3 -3
  263. agno/utils/message.py +60 -0
  264. agno/utils/models/ai_foundry.py +9 -2
  265. agno/utils/models/claude.py +49 -14
  266. agno/utils/models/cohere.py +9 -2
  267. agno/utils/models/llama.py +9 -2
  268. agno/utils/models/mistral.py +4 -2
  269. agno/utils/print_response/agent.py +109 -16
  270. agno/utils/print_response/team.py +223 -30
  271. agno/utils/print_response/workflow.py +251 -34
  272. agno/utils/streamlit.py +1 -1
  273. agno/utils/team.py +98 -9
  274. agno/utils/tokens.py +657 -0
  275. agno/vectordb/base.py +39 -7
  276. agno/vectordb/cassandra/cassandra.py +21 -5
  277. agno/vectordb/chroma/chromadb.py +43 -12
  278. agno/vectordb/clickhouse/clickhousedb.py +21 -5
  279. agno/vectordb/couchbase/couchbase.py +29 -5
  280. agno/vectordb/lancedb/lance_db.py +92 -181
  281. agno/vectordb/langchaindb/langchaindb.py +24 -4
  282. agno/vectordb/lightrag/lightrag.py +17 -3
  283. agno/vectordb/llamaindex/llamaindexdb.py +25 -5
  284. agno/vectordb/milvus/milvus.py +50 -37
  285. agno/vectordb/mongodb/__init__.py +7 -1
  286. agno/vectordb/mongodb/mongodb.py +36 -30
  287. agno/vectordb/pgvector/pgvector.py +201 -77
  288. agno/vectordb/pineconedb/pineconedb.py +41 -23
  289. agno/vectordb/qdrant/qdrant.py +67 -54
  290. agno/vectordb/redis/__init__.py +9 -0
  291. agno/vectordb/redis/redisdb.py +682 -0
  292. agno/vectordb/singlestore/singlestore.py +50 -29
  293. agno/vectordb/surrealdb/surrealdb.py +31 -41
  294. agno/vectordb/upstashdb/upstashdb.py +34 -6
  295. agno/vectordb/weaviate/weaviate.py +53 -14
  296. agno/workflow/__init__.py +2 -0
  297. agno/workflow/agent.py +299 -0
  298. agno/workflow/condition.py +120 -18
  299. agno/workflow/loop.py +77 -10
  300. agno/workflow/parallel.py +231 -143
  301. agno/workflow/router.py +118 -17
  302. agno/workflow/step.py +609 -170
  303. agno/workflow/steps.py +73 -6
  304. agno/workflow/types.py +96 -21
  305. agno/workflow/workflow.py +2039 -262
  306. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
  307. agno-2.3.13.dist-info/RECORD +613 -0
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -679
  310. agno/tools/memori.py +0 -339
  311. agno-2.1.2.dist-info/RECORD +0 -543
  312. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
  313. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/os/app.py CHANGED
@@ -12,9 +12,11 @@ from rich.panel import Panel
12
12
  from starlette.requests import Request
13
13
 
14
14
  from agno.agent.agent import Agent
15
- from agno.db.base import BaseDb
15
+ from agno.db.base import AsyncBaseDb, BaseDb
16
+ from agno.knowledge.knowledge import Knowledge
16
17
  from agno.os.config import (
17
18
  AgentOSConfig,
19
+ AuthorizationConfig,
18
20
  DatabaseConfig,
19
21
  EvalsConfig,
20
22
  EvalsDomainConfig,
@@ -26,9 +28,12 @@ from agno.os.config import (
26
28
  MetricsDomainConfig,
27
29
  SessionConfig,
28
30
  SessionDomainConfig,
31
+ TracesConfig,
32
+ TracesDomainConfig,
29
33
  )
30
34
  from agno.os.interfaces.base import BaseInterface
31
- from agno.os.router import get_base_router, get_websocket_router
35
+ from agno.os.router import get_base_router
36
+ from agno.os.routers.agents import get_agent_router
32
37
  from agno.os.routers.evals import get_eval_router
33
38
  from agno.os.routers.health import get_health_router
34
39
  from agno.os.routers.home import get_home_router
@@ -36,16 +41,21 @@ from agno.os.routers.knowledge import get_knowledge_router
36
41
  from agno.os.routers.memory import get_memory_router
37
42
  from agno.os.routers.metrics import get_metrics_router
38
43
  from agno.os.routers.session import get_session_router
44
+ from agno.os.routers.teams import get_team_router
45
+ from agno.os.routers.traces import get_traces_router
46
+ from agno.os.routers.workflows import get_websocket_router, get_workflow_router
39
47
  from agno.os.settings import AgnoAPISettings
40
48
  from agno.os.utils import (
41
49
  collect_mcp_tools_from_team,
42
50
  collect_mcp_tools_from_workflow,
43
51
  find_conflicting_routes,
44
52
  load_yaml_config,
53
+ resolve_origins,
54
+ setup_tracing_for_os,
45
55
  update_cors_middleware,
46
56
  )
47
57
  from agno.team.team import Team
48
- from agno.utils.log import logger
58
+ from agno.utils.log import log_debug, log_error, log_info, log_warning
49
59
  from agno.utils.string import generate_id, generate_id_from_name
50
60
  from agno.workflow.workflow import Workflow
51
61
 
@@ -64,6 +74,39 @@ async def mcp_lifespan(_, mcp_tools):
64
74
  await tool.close()
65
75
 
66
76
 
77
+ @asynccontextmanager
78
+ async def db_lifespan(app: FastAPI, agent_os: "AgentOS"):
79
+ """Initializes databases in the event loop"""
80
+ if agent_os.auto_provision_dbs:
81
+ agent_os._initialize_sync_databases()
82
+ await agent_os._initialize_async_databases()
83
+ yield
84
+
85
+
86
+ def _combine_app_lifespans(lifespans: list) -> Any:
87
+ """Combine multiple FastAPI app lifespan context managers into one."""
88
+ if len(lifespans) == 1:
89
+ return lifespans[0]
90
+
91
+ from contextlib import asynccontextmanager
92
+
93
+ @asynccontextmanager
94
+ async def combined_lifespan(app):
95
+ async def _run_nested(index: int):
96
+ if index >= len(lifespans):
97
+ yield
98
+ return
99
+
100
+ async with lifespans[index](app):
101
+ async for _ in _run_nested(index + 1):
102
+ yield
103
+
104
+ async for _ in _run_nested(0):
105
+ yield
106
+
107
+ return combined_lifespan
108
+
109
+
67
110
  class AgentOS:
68
111
  def __init__(
69
112
  self,
@@ -74,19 +117,23 @@ class AgentOS:
74
117
  agents: Optional[List[Agent]] = None,
75
118
  teams: Optional[List[Team]] = None,
76
119
  workflows: Optional[List[Workflow]] = None,
120
+ knowledge: Optional[List[Knowledge]] = None,
77
121
  interfaces: Optional[List[BaseInterface]] = None,
78
122
  a2a_interface: bool = False,
123
+ authorization: bool = False,
124
+ authorization_config: Optional[AuthorizationConfig] = None,
125
+ cors_allowed_origins: Optional[List[str]] = None,
79
126
  config: Optional[Union[str, AgentOSConfig]] = None,
80
127
  settings: Optional[AgnoAPISettings] = None,
81
128
  lifespan: Optional[Any] = None,
82
129
  enable_mcp_server: bool = False,
83
130
  base_app: Optional[FastAPI] = None,
84
131
  on_route_conflict: Literal["preserve_agentos", "preserve_base_app", "error"] = "preserve_agentos",
132
+ tracing: bool = False,
133
+ tracing_db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
134
+ auto_provision_dbs: bool = True,
135
+ run_hooks_in_background: bool = False,
85
136
  telemetry: bool = True,
86
- os_id: Optional[str] = None, # Deprecated
87
- enable_mcp: bool = False, # Deprecated
88
- fastapi_app: Optional[FastAPI] = None, # Deprecated
89
- replace_routes: Optional[bool] = None, # Deprecated
90
137
  ):
91
138
  """Initialize AgentOS.
92
139
 
@@ -98,6 +145,7 @@ class AgentOS:
98
145
  agents: List of agents to include in the OS
99
146
  teams: List of teams to include in the OS
100
147
  workflows: List of workflows to include in the OS
148
+ knowledge: List of knowledge bases to include in the OS
101
149
  interfaces: List of interfaces to include in the OS
102
150
  a2a_interface: Whether to expose the OS agents and teams in an A2A server
103
151
  config: Configuration file path or AgentOSConfig instance
@@ -106,11 +154,19 @@ class AgentOS:
106
154
  enable_mcp_server: Whether to enable MCP (Model Context Protocol)
107
155
  base_app: Optional base FastAPI app to use for the AgentOS. All routes and middleware will be added to this app.
108
156
  on_route_conflict: What to do when a route conflict is detected in case a custom base_app is provided.
157
+ auto_provision_dbs: Whether to automatically provision databases
158
+ authorization: Whether to enable authorization
159
+ authorization_config: Configuration for the authorization middleware
160
+ cors_allowed_origins: List of allowed CORS origins (will be merged with default Agno domains)
161
+ tracing: If True, enables OpenTelemetry tracing for all agents and teams in the OS
162
+ tracing_db: Dedicated database for storing and reading traces. Recommended for multi-db setups.
163
+ If not provided and tracing=True, the first available db from agents/teams/workflows is used.
164
+ run_hooks_in_background: If True, run agent/team pre/post hooks as FastAPI background tasks (non-blocking)
109
165
  telemetry: Whether to enable telemetry
110
166
 
111
167
  """
112
- if not agents and not workflows and not teams:
113
- raise ValueError("Either agents, teams or workflows must be provided.")
168
+ if not agents and not workflows and not teams and not knowledge:
169
+ raise ValueError("Either agents, teams, workflows or knowledge bases must be provided.")
114
170
 
115
171
  self.config = load_yaml_config(config) if isinstance(config, str) else config
116
172
 
@@ -119,22 +175,15 @@ class AgentOS:
119
175
  self.teams: Optional[List[Team]] = teams
120
176
  self.interfaces = interfaces or []
121
177
  self.a2a_interface = a2a_interface
122
-
178
+ self.knowledge = knowledge
123
179
  self.settings: AgnoAPISettings = settings or AgnoAPISettings()
124
-
180
+ self.auto_provision_dbs = auto_provision_dbs
125
181
  self._app_set = False
126
182
 
127
183
  if base_app:
128
184
  self.base_app: Optional[FastAPI] = base_app
129
185
  self._app_set = True
130
186
  self.on_route_conflict = on_route_conflict
131
- elif fastapi_app:
132
- self.base_app = fastapi_app
133
- self._app_set = True
134
- if replace_routes is not None:
135
- self.on_route_conflict = "preserve_agentos" if replace_routes else "preserve_base_app"
136
- else:
137
- self.on_route_conflict = on_route_conflict
138
187
  else:
139
188
  self.base_app = None
140
189
  self._app_set = False
@@ -144,7 +193,7 @@ class AgentOS:
144
193
 
145
194
  self.name = name
146
195
 
147
- self.id = id or os_id
196
+ self.id = id
148
197
  if not self.id:
149
198
  self.id = generate_id(self.name) if self.name else str(uuid4())
150
199
 
@@ -152,58 +201,138 @@ class AgentOS:
152
201
  self.description = description
153
202
 
154
203
  self.telemetry = telemetry
204
+ self.tracing = tracing
205
+ self.tracing_db = tracing_db
155
206
 
156
- self.enable_mcp_server = enable_mcp or enable_mcp_server
207
+ self.enable_mcp_server = enable_mcp_server
157
208
  self.lifespan = lifespan
158
209
 
210
+ # RBAC
211
+ self.authorization = authorization
212
+ self.authorization_config = authorization_config
213
+
214
+ # CORS configuration - merge user-provided origins with defaults from settings
215
+ self.cors_allowed_origins = resolve_origins(cors_allowed_origins, self.settings.cors_origin_list)
216
+
217
+ # If True, run agent/team hooks as FastAPI background tasks
218
+ self.run_hooks_in_background = run_hooks_in_background
219
+
159
220
  # List of all MCP tools used inside the AgentOS
160
221
  self.mcp_tools: List[Any] = []
161
222
  self._mcp_app: Optional[Any] = None
162
223
 
163
- if self.agents:
164
- for agent in self.agents:
165
- # Track all MCP tools to later handle their connection
166
- if agent.tools:
167
- for tool in agent.tools:
168
- # Checking if the tool is a MCPTools or MultiMCPTools instance
169
- type_name = type(tool).__name__
170
- if type_name in ("MCPTools", "MultiMCPTools"):
171
- if tool not in self.mcp_tools:
172
- self.mcp_tools.append(tool)
224
+ self._initialize_agents()
225
+ self._initialize_teams()
226
+ self._initialize_workflows()
173
227
 
174
- agent.initialize_agent()
228
+ if self.tracing:
229
+ self._setup_tracing()
175
230
 
176
- # Required for the built-in routes to work
177
- agent.store_events = True
231
+ if self.telemetry:
232
+ from agno.api.os import OSLaunch, log_os_telemetry
178
233
 
179
- if self.teams:
180
- for team in self.teams:
181
- # Track all MCP tools recursively
182
- collect_mcp_tools_from_team(team, self.mcp_tools)
234
+ log_os_telemetry(launch=OSLaunch(os_id=self.id, data=self._get_telemetry_data()))
183
235
 
184
- team.initialize_team()
236
+ def _add_agent_os_to_lifespan_function(self, lifespan):
237
+ """
238
+ Inspect a lifespan function and wrap it to pass agent_os if it accepts it.
185
239
 
186
- # Required for the built-in routes to work
187
- team.store_events = True
240
+ Returns:
241
+ A wrapped lifespan that passes agent_os if the lifespan function expects it.
242
+ """
243
+ # Getting the actual function inside the lifespan
244
+ lifespan_function = lifespan
245
+ if hasattr(lifespan, "__wrapped__"):
246
+ lifespan_function = lifespan.__wrapped__
188
247
 
189
- for member in team.members:
190
- if isinstance(member, Agent):
191
- member.team_id = None
192
- member.initialize_agent()
193
- elif isinstance(member, Team):
194
- member.initialize_team()
248
+ try:
249
+ from inspect import signature
195
250
 
196
- if self.workflows:
197
- for workflow in self.workflows:
198
- # Track MCP tools recursively in workflow members
199
- collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
200
- if not workflow.id:
201
- workflow.id = generate_id_from_name(workflow.name)
251
+ # Inspecting the lifespan function signature to find its parameters
252
+ sig = signature(lifespan_function)
253
+ params = list(sig.parameters.keys())
202
254
 
203
- if self.telemetry:
204
- from agno.api.os import OSLaunch, log_os_telemetry
255
+ # If the lifespan function expects the 'agent_os' parameter, add it
256
+ if "agent_os" in params:
257
+ return partial(lifespan, agent_os=self)
258
+ else:
259
+ return lifespan
205
260
 
206
- log_os_telemetry(launch=OSLaunch(os_id=self.id, data=self._get_telemetry_data()))
261
+ except (ValueError, TypeError):
262
+ return lifespan
263
+
264
+ def resync(self, app: FastAPI) -> None:
265
+ """Resync the AgentOS to discover, initialize and configure: agents, teams, workflows, databases and knowledge bases."""
266
+ self._initialize_agents()
267
+ self._initialize_teams()
268
+ self._initialize_workflows()
269
+ self._auto_discover_databases()
270
+ self._auto_discover_knowledge_instances()
271
+
272
+ if self.enable_mcp_server:
273
+ from agno.os.mcp import get_mcp_server
274
+
275
+ self._mcp_app = get_mcp_server(self)
276
+
277
+ self._reprovision_routers(app=app)
278
+
279
+ def _reprovision_routers(self, app: FastAPI) -> None:
280
+ """Re-provision all routes for the AgentOS."""
281
+ updated_routers = [
282
+ get_session_router(dbs=self.dbs),
283
+ get_metrics_router(dbs=self.dbs),
284
+ get_knowledge_router(knowledge_instances=self.knowledge_instances),
285
+ get_traces_router(dbs=self.dbs),
286
+ get_memory_router(dbs=self.dbs),
287
+ get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
288
+ ]
289
+
290
+ # Clear all previously existing routes
291
+ app.router.routes = [
292
+ route
293
+ for route in app.router.routes
294
+ if hasattr(route, "path")
295
+ and route.path in ["/docs", "/redoc", "/openapi.json", "/docs/oauth2-redirect"]
296
+ or route.path.startswith("/mcp") # type: ignore
297
+ ]
298
+
299
+ # Add the built-in routes
300
+ self._add_built_in_routes(app=app)
301
+
302
+ # Add the updated routes
303
+ for router in updated_routers:
304
+ self._add_router(app, router)
305
+
306
+ # Mount MCP if needed
307
+ if self.enable_mcp_server and self._mcp_app:
308
+ app.mount("/", self._mcp_app)
309
+
310
+ def _add_built_in_routes(self, app: FastAPI) -> None:
311
+ """Add all AgentOSbuilt-in routes to the given app."""
312
+ # Add the home router if MCP server is not enabled
313
+ if not self.enable_mcp_server:
314
+ self._add_router(app, get_home_router(self))
315
+
316
+ self._add_router(app, get_health_router(health_endpoint="/health"))
317
+ self._add_router(app, get_base_router(self, settings=self.settings))
318
+ self._add_router(app, get_agent_router(self, settings=self.settings))
319
+ self._add_router(app, get_team_router(self, settings=self.settings))
320
+ self._add_router(app, get_workflow_router(self, settings=self.settings))
321
+ self._add_router(app, get_websocket_router(self, settings=self.settings))
322
+
323
+ # Add A2A interface if relevant
324
+ has_a2a_interface = False
325
+ for interface in self.interfaces:
326
+ if not has_a2a_interface and interface.__class__.__name__ == "A2A":
327
+ has_a2a_interface = True
328
+ interface_router = interface.get_router()
329
+ self._add_router(app, interface_router)
330
+ if self.a2a_interface and not has_a2a_interface:
331
+ from agno.os.interfaces.a2a import A2A
332
+
333
+ a2a_interface = A2A(agents=self.agents, teams=self.teams, workflows=self.workflows)
334
+ self.interfaces.append(a2a_interface)
335
+ self._add_router(app, a2a_interface.get_router())
207
336
 
208
337
  def _make_app(self, lifespan: Optional[Any] = None) -> FastAPI:
209
338
  # Adjust the FastAPI app lifespan to handle MCP connections if relevant
@@ -220,7 +349,7 @@ class AgentOS:
220
349
  async with mcp_tools_lifespan(app): # type: ignore
221
350
  yield
222
351
 
223
- app_lifespan = combined_lifespan # type: ignore
352
+ app_lifespan = combined_lifespan
224
353
  else:
225
354
  app_lifespan = mcp_tools_lifespan
226
355
 
@@ -234,53 +363,175 @@ class AgentOS:
234
363
  lifespan=app_lifespan,
235
364
  )
236
365
 
366
+ def _initialize_agents(self) -> None:
367
+ """Initialize and configure all agents for AgentOS usage."""
368
+ if not self.agents:
369
+ return
370
+ for agent in self.agents:
371
+ # Track all MCP tools to later handle their connection
372
+ if agent.tools:
373
+ for tool in agent.tools:
374
+ # Checking if the tool is an instance of MCPTools, MultiMCPTools, or a subclass of those
375
+ if hasattr(type(tool), "__mro__"):
376
+ mro_names = {cls.__name__ for cls in type(tool).__mro__}
377
+ if mro_names & {"MCPTools", "MultiMCPTools"}:
378
+ if tool not in self.mcp_tools:
379
+ self.mcp_tools.append(tool)
380
+
381
+ agent.initialize_agent()
382
+
383
+ # Required for the built-in routes to work
384
+ agent.store_events = True
385
+
386
+ # Propagate run_hooks_in_background setting from AgentOS to agents
387
+ agent._run_hooks_in_background = self.run_hooks_in_background
388
+
389
+ def _initialize_teams(self) -> None:
390
+ """Initialize and configure all teams for AgentOS usage."""
391
+ if not self.teams:
392
+ return
393
+
394
+ for team in self.teams:
395
+ # Track all MCP tools recursively
396
+ collect_mcp_tools_from_team(team, self.mcp_tools)
397
+
398
+ team.initialize_team()
399
+
400
+ for member in team.members:
401
+ if isinstance(member, Agent):
402
+ member.team_id = None
403
+ member.initialize_agent()
404
+ elif isinstance(member, Team):
405
+ member.initialize_team()
406
+
407
+ # Required for the built-in routes to work
408
+ team.store_events = True
409
+
410
+ # Propagate run_hooks_in_background setting to team and all nested members
411
+ team.propagate_run_hooks_in_background(self.run_hooks_in_background)
412
+
413
+ def _initialize_workflows(self) -> None:
414
+ """Initialize and configure all workflows for AgentOS usage."""
415
+ if not self.workflows:
416
+ return
417
+
418
+ if self.workflows:
419
+ for workflow in self.workflows:
420
+ # Track MCP tools recursively in workflow members
421
+ collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
422
+
423
+ if not workflow.id:
424
+ workflow.id = generate_id_from_name(workflow.name)
425
+
426
+ # Required for the built-in routes to work
427
+ workflow.store_events = True
428
+
429
+ # Propagate run_hooks_in_background setting to workflow and all its step agents/teams
430
+ workflow.propagate_run_hooks_in_background(self.run_hooks_in_background)
431
+
432
+ def _setup_tracing(self) -> None:
433
+ """Set up OpenTelemetry tracing for this AgentOS.
434
+
435
+ Uses tracing_db if provided, otherwise falls back to the first available
436
+ database from agents/teams/workflows.
437
+ """
438
+ # Use tracing_db if explicitly provided
439
+ if self.tracing_db is not None:
440
+ setup_tracing_for_os(db=self.tracing_db)
441
+ return
442
+
443
+ # Fall back to finding the first available database
444
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None
445
+
446
+ for agent in self.agents or []:
447
+ if agent.db:
448
+ db = agent.db
449
+ break
450
+
451
+ if db is None:
452
+ for team in self.teams or []:
453
+ if team.db:
454
+ db = team.db
455
+ break
456
+
457
+ if db is None:
458
+ for workflow in self.workflows or []:
459
+ if workflow.db:
460
+ db = workflow.db
461
+ break
462
+
463
+ if db is None:
464
+ log_warning(
465
+ "tracing=True but no database found. "
466
+ "Provide 'tracing_db' parameter or 'db' parameter to at least one agent/team/workflow."
467
+ )
468
+ return
469
+
470
+ setup_tracing_for_os(db=db)
471
+
237
472
  def get_app(self) -> FastAPI:
238
473
  if self.base_app:
239
474
  fastapi_app = self.base_app
240
- else:
241
- if self.enable_mcp_server:
242
- from contextlib import asynccontextmanager
243
475
 
476
+ # Initialize MCP server if enabled
477
+ if self.enable_mcp_server:
244
478
  from agno.os.mcp import get_mcp_server
245
479
 
246
480
  self._mcp_app = get_mcp_server(self)
247
481
 
248
- final_lifespan = self._mcp_app.lifespan # type: ignore
249
- if self.lifespan is not None:
250
- # Combine both lifespans
251
- @asynccontextmanager
252
- async def combined_lifespan(app: FastAPI):
253
- # Run both lifespans
254
- async with self.lifespan(app): # type: ignore
255
- async with self._mcp_app.lifespan(app): # type: ignore
256
- yield
482
+ # Collect all lifespans that need to be combined
483
+ lifespans = []
257
484
 
258
- final_lifespan = combined_lifespan # type: ignore
485
+ # The user provided lifespan
486
+ if self.lifespan:
487
+ # Wrap the user lifespan with agent_os parameter
488
+ wrapped_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
489
+ lifespans.append(wrapped_lifespan)
259
490
 
260
- fastapi_app = self._make_app(lifespan=final_lifespan)
261
- else:
262
- fastapi_app = self._make_app(lifespan=self.lifespan)
491
+ # The provided app's existing lifespan
492
+ if fastapi_app.router.lifespan_context:
493
+ lifespans.append(fastapi_app.router.lifespan_context)
263
494
 
264
- # Add routes
265
- self._add_router(fastapi_app, get_base_router(self, settings=self.settings))
266
- self._add_router(fastapi_app, get_websocket_router(self, settings=self.settings))
267
- self._add_router(fastapi_app, get_health_router())
268
- self._add_router(fastapi_app, get_home_router(self))
495
+ # The MCP tools lifespan
496
+ if self.mcp_tools:
497
+ lifespans.append(partial(mcp_lifespan, mcp_tools=self.mcp_tools))
269
498
 
270
- has_a2a_interface = False
271
- for interface in self.interfaces:
272
- if not has_a2a_interface and interface.__class__.__name__ == "A2A":
273
- has_a2a_interface = True
274
- interface_router = interface.get_router()
275
- self._add_router(fastapi_app, interface_router)
499
+ # The /mcp server lifespan
500
+ if self.enable_mcp_server and self._mcp_app:
501
+ lifespans.append(self._mcp_app.lifespan)
276
502
 
277
- # Add A2A interface if requested and not provided in self.interfaces
278
- if self.a2a_interface and not has_a2a_interface:
279
- from agno.os.interfaces.a2a import A2A
503
+ # The async database lifespan
504
+ lifespans.append(partial(db_lifespan, agent_os=self))
280
505
 
281
- a2a_interface = A2A(agents=self.agents, teams=self.teams, workflows=self.workflows)
282
- self.interfaces.append(a2a_interface)
283
- self._add_router(fastapi_app, a2a_interface.get_router())
506
+ # Combine lifespans and set them in the app
507
+ if lifespans:
508
+ fastapi_app.router.lifespan_context = _combine_app_lifespans(lifespans)
509
+
510
+ else:
511
+ lifespans = []
512
+
513
+ # User provided lifespan
514
+ if self.lifespan:
515
+ lifespans.append(self._add_agent_os_to_lifespan_function(self.lifespan))
516
+
517
+ # MCP tools lifespan
518
+ if self.mcp_tools:
519
+ lifespans.append(partial(mcp_lifespan, mcp_tools=self.mcp_tools))
520
+
521
+ # MCP server lifespan
522
+ if self.enable_mcp_server:
523
+ from agno.os.mcp import get_mcp_server
524
+
525
+ self._mcp_app = get_mcp_server(self)
526
+ lifespans.append(self._mcp_app.lifespan)
527
+
528
+ # Async database initialization lifespan
529
+ lifespans.append(partial(db_lifespan, agent_os=self)) # type: ignore
530
+
531
+ final_lifespan = _combine_app_lifespans(lifespans) if lifespans else None
532
+ fastapi_app = self._make_app(lifespan=final_lifespan)
533
+
534
+ self._add_built_in_routes(app=fastapi_app)
284
535
 
285
536
  self._auto_discover_databases()
286
537
  self._auto_discover_knowledge_instances()
@@ -291,6 +542,7 @@ class AgentOS:
291
542
  get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
292
543
  get_metrics_router(dbs=self.dbs),
293
544
  get_knowledge_router(knowledge_instances=self.knowledge_instances),
545
+ get_traces_router(dbs=self.dbs),
294
546
  ]
295
547
 
296
548
  for router in routers:
@@ -299,35 +551,76 @@ class AgentOS:
299
551
  # Mount MCP if needed
300
552
  if self.enable_mcp_server and self._mcp_app:
301
553
  fastapi_app.mount("/", self._mcp_app)
302
- else:
303
- # Add the home router
304
- self._add_router(fastapi_app, get_home_router(self))
305
554
 
306
555
  if not self._app_set:
307
556
 
308
557
  @fastapi_app.exception_handler(HTTPException)
309
558
  async def http_exception_handler(_, exc: HTTPException) -> JSONResponse:
559
+ log_error(f"HTTP exception: {exc.status_code} {exc.detail}")
310
560
  return JSONResponse(
311
561
  status_code=exc.status_code,
312
562
  content={"detail": str(exc.detail)},
313
563
  )
314
564
 
315
- async def general_exception_handler(request: Request, call_next):
316
- try:
317
- return await call_next(request)
318
- except Exception as e:
319
- return JSONResponse(
320
- status_code=e.status_code if hasattr(e, "status_code") else 500, # type: ignore
321
- content={"detail": str(e)},
322
- )
565
+ @fastapi_app.exception_handler(Exception)
566
+ async def general_exception_handler(_: Request, exc: Exception) -> JSONResponse:
567
+ import traceback
323
568
 
324
- fastapi_app.middleware("http")(general_exception_handler)
569
+ log_error(f"Unhandled exception:\n{traceback.format_exc(limit=5)}")
570
+
571
+ return JSONResponse(
572
+ status_code=getattr(exc, "status_code", 500),
573
+ content={"detail": str(exc)},
574
+ )
325
575
 
326
576
  # Update CORS middleware
327
- update_cors_middleware(fastapi_app, self.settings.cors_origin_list) # type: ignore
577
+ update_cors_middleware(fastapi_app, self.cors_allowed_origins) # type: ignore
578
+
579
+ # Set agent_os_id and cors_allowed_origins on app state
580
+ # This allows middleware (like JWT) to access these values
581
+ fastapi_app.state.agent_os_id = self.id
582
+ fastapi_app.state.cors_allowed_origins = self.cors_allowed_origins
583
+
584
+ # Add JWT middleware if authorization is enabled
585
+ if self.authorization:
586
+ self._add_jwt_middleware(fastapi_app)
328
587
 
329
588
  return fastapi_app
330
589
 
590
+ def _add_jwt_middleware(self, fastapi_app: FastAPI) -> None:
591
+ from agno.os.middleware.jwt import JWTMiddleware, JWTValidator
592
+
593
+ verify_audience = False
594
+ jwks_file = None
595
+ verification_keys = None
596
+ algorithm = "RS256"
597
+
598
+ if self.authorization_config:
599
+ algorithm = self.authorization_config.algorithm or "RS256"
600
+ verification_keys = self.authorization_config.verification_keys
601
+ jwks_file = self.authorization_config.jwks_file
602
+ verify_audience = self.authorization_config.verify_audience or False
603
+
604
+ log_info(f"Adding JWT middleware for authorization (algorithm: {algorithm})")
605
+
606
+ # Create validator and store on app.state for WebSocket access
607
+ jwt_validator = JWTValidator(
608
+ verification_keys=verification_keys,
609
+ jwks_file=jwks_file,
610
+ algorithm=algorithm,
611
+ )
612
+ fastapi_app.state.jwt_validator = jwt_validator
613
+
614
+ # Add middleware to stack
615
+ fastapi_app.add_middleware(
616
+ JWTMiddleware,
617
+ verification_keys=verification_keys,
618
+ jwks_file=jwks_file,
619
+ algorithm=algorithm,
620
+ authorization=self.authorization,
621
+ verify_audience=verify_audience,
622
+ )
623
+
331
624
  def get_routes(self) -> List[Any]:
332
625
  """Retrieve all routes from the FastAPI app.
333
626
 
@@ -353,7 +646,7 @@ class AgentOS:
353
646
  # Skip conflicting AgentOS routes, prefer user's existing routes
354
647
  for conflict in conflicts:
355
648
  methods_str = ", ".join(conflict["methods"]) # type: ignore
356
- logger.debug(
649
+ log_debug(
357
650
  f"Skipping conflicting AgentOS route: {methods_str} {conflict['path']} - "
358
651
  f"Using existing custom route instead"
359
652
  )
@@ -372,7 +665,7 @@ class AgentOS:
372
665
  # Log warnings but still add all routes (AgentOS routes will override)
373
666
  for conflict in conflicts:
374
667
  methods_str = ", ".join(conflict["methods"]) # type: ignore
375
- logger.warning(
668
+ log_warning(
376
669
  f"Route conflict detected: {methods_str} {conflict['path']} - "
377
670
  f"AgentOS route will override existing custom route"
378
671
  )
@@ -404,11 +697,12 @@ class AgentOS:
404
697
  }
405
698
 
406
699
  def _auto_discover_databases(self) -> None:
407
- """Auto-discover the databases used by all contextual agents, teams and workflows."""
408
- from agno.db.base import BaseDb
700
+ """Auto-discover and initialize the databases used by all contextual agents, teams and workflows."""
409
701
 
410
- dbs: Dict[str, BaseDb] = {}
411
- knowledge_dbs: Dict[str, BaseDb] = {} # Track databases specifically used for knowledge
702
+ dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb]]] = {}
703
+ knowledge_dbs: Dict[
704
+ str, List[Union[BaseDb, AsyncBaseDb]]
705
+ ] = {} # Track databases specifically used for knowledge
412
706
 
413
707
  for agent in self.agents or []:
414
708
  if agent.db:
@@ -426,68 +720,120 @@ class AgentOS:
426
720
  if workflow.db:
427
721
  self._register_db_with_validation(dbs, workflow.db)
428
722
 
723
+ for knowledge_base in self.knowledge or []:
724
+ if knowledge_base.contents_db:
725
+ self._register_db_with_validation(knowledge_dbs, knowledge_base.contents_db)
726
+
429
727
  for interface in self.interfaces or []:
430
728
  if interface.agent and interface.agent.db:
431
729
  self._register_db_with_validation(dbs, interface.agent.db)
432
730
  elif interface.team and interface.team.db:
433
731
  self._register_db_with_validation(dbs, interface.team.db)
434
732
 
733
+ # Register tracing_db if provided (for traces reading)
734
+ if self.tracing_db is not None:
735
+ self._register_db_with_validation(dbs, self.tracing_db)
736
+
435
737
  self.dbs = dbs
436
738
  self.knowledge_dbs = knowledge_dbs
437
739
 
438
- def _register_db_with_validation(self, registered_dbs: Dict[str, Any], db: BaseDb) -> None:
439
- """Register a database in the contextual OS after validating it is not conflicting with registered databases"""
440
- if db.id in registered_dbs:
441
- existing_db = registered_dbs[db.id]
442
- if not self._are_db_instances_compatible(existing_db, db):
443
- raise ValueError(
444
- f"Database ID conflict detected: Two different database instances have the same ID '{db.id}'. "
445
- f"Database instances with the same ID must point to the same database with identical configuration."
740
+ # Initialize all discovered databases
741
+ if self.auto_provision_dbs:
742
+ self._pending_async_db_init = True
743
+
744
+ def _initialize_sync_databases(self) -> None:
745
+ """Initialize sync databases."""
746
+ from itertools import chain
747
+
748
+ unique_dbs = list(
749
+ {
750
+ id(db): db
751
+ for db in chain(
752
+ chain.from_iterable(self.dbs.values()), chain.from_iterable(self.knowledge_dbs.values())
446
753
  )
447
- registered_dbs[db.id] = db
754
+ }.values()
755
+ )
448
756
 
449
- def _are_db_instances_compatible(self, db1: BaseDb, db2: BaseDb) -> bool:
450
- """
451
- Return True if the two given database objects are compatible
452
- Two database objects are compatible if they point to the same database with identical configuration.
453
- """
454
- # If they're the same object reference, they're compatible
455
- if db1 is db2:
456
- return True
757
+ for db in unique_dbs:
758
+ if isinstance(db, AsyncBaseDb):
759
+ continue # Skip async dbs
457
760
 
458
- if type(db1) is not type(db2):
459
- return False
761
+ try:
762
+ if hasattr(db, "_create_all_tables") and callable(db._create_all_tables):
763
+ db._create_all_tables()
764
+ except Exception as e:
765
+ log_warning(f"Failed to initialize {db.__class__.__name__} (id: {db.id}): {e}")
460
766
 
461
- if hasattr(db1, "db_url") and hasattr(db2, "db_url"):
462
- if db1.db_url != db2.db_url: # type: ignore
463
- return False
767
+ async def _initialize_async_databases(self) -> None:
768
+ """Initialize async databases."""
464
769
 
465
- if hasattr(db1, "db_file") and hasattr(db2, "db_file"):
466
- if db1.db_file != db2.db_file: # type: ignore
467
- return False
770
+ from itertools import chain
468
771
 
469
- # If table names are different, they're not compatible
470
- if (
471
- db1.session_table_name != db2.session_table_name
472
- or db1.memory_table_name != db2.memory_table_name
473
- or db1.metrics_table_name != db2.metrics_table_name
474
- or db1.eval_table_name != db2.eval_table_name
475
- or db1.knowledge_table_name != db2.knowledge_table_name
476
- ):
477
- return False
772
+ unique_dbs = list(
773
+ {
774
+ id(db): db
775
+ for db in chain(
776
+ chain.from_iterable(self.dbs.values()), chain.from_iterable(self.knowledge_dbs.values())
777
+ )
778
+ }.values()
779
+ )
478
780
 
479
- return True
781
+ for db in unique_dbs:
782
+ if not isinstance(db, AsyncBaseDb):
783
+ continue # Skip sync dbs
784
+
785
+ try:
786
+ if hasattr(db, "_create_all_tables") and callable(db._create_all_tables):
787
+ await db._create_all_tables()
788
+ except Exception as e:
789
+ log_warning(f"Failed to initialize async {db.__class__.__name__} (id: {db.id}): {e}")
790
+
791
+ def _get_db_table_names(self, db: BaseDb) -> Dict[str, str]:
792
+ """Get the table names for a database"""
793
+ table_names = {
794
+ "session_table_name": db.session_table_name,
795
+ "culture_table_name": db.culture_table_name,
796
+ "memory_table_name": db.memory_table_name,
797
+ "metrics_table_name": db.metrics_table_name,
798
+ "evals_table_name": db.eval_table_name,
799
+ "knowledge_table_name": db.knowledge_table_name,
800
+ }
801
+ return {k: v for k, v in table_names.items() if v is not None}
802
+
803
+ def _register_db_with_validation(
804
+ self, registered_dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb]]], db: Union[BaseDb, AsyncBaseDb]
805
+ ) -> None:
806
+ """Register a database in the contextual OS after validating it is not conflicting with registered databases"""
807
+ if db.id in registered_dbs:
808
+ registered_dbs[db.id].append(db)
809
+ else:
810
+ registered_dbs[db.id] = [db]
480
811
 
481
812
  def _auto_discover_knowledge_instances(self) -> None:
482
813
  """Auto-discover the knowledge instances used by all contextual agents, teams and workflows."""
483
- knowledge_instances = []
814
+ seen_ids = set()
815
+ knowledge_instances: List[Knowledge] = []
816
+
817
+ def _add_knowledge_if_not_duplicate(knowledge: "Knowledge") -> None:
818
+ """Add knowledge instance if it's not already in the list (by object identity or db_id)."""
819
+ # Use database ID if available, otherwise use object ID as fallback
820
+ if not knowledge.contents_db:
821
+ return
822
+ if knowledge.contents_db.id in seen_ids:
823
+ return
824
+ seen_ids.add(knowledge.contents_db.id)
825
+ knowledge_instances.append(knowledge)
826
+
484
827
  for agent in self.agents or []:
485
828
  if agent.knowledge:
486
- knowledge_instances.append(agent.knowledge)
829
+ _add_knowledge_if_not_duplicate(agent.knowledge)
487
830
 
488
831
  for team in self.teams or []:
489
832
  if team.knowledge:
490
- knowledge_instances.append(team.knowledge)
833
+ _add_knowledge_if_not_duplicate(team.knowledge)
834
+
835
+ for knowledge_base in self.knowledge or []:
836
+ _add_knowledge_if_not_duplicate(knowledge_base)
491
837
 
492
838
  self.knowledge_instances = knowledge_instances
493
839
 
@@ -498,13 +844,15 @@ class AgentOS:
498
844
  session_config.dbs = []
499
845
 
500
846
  dbs_with_specific_config = [db.db_id for db in session_config.dbs]
501
-
502
- for db_id in self.dbs.keys():
847
+ for db_id, dbs in self.dbs.items():
503
848
  if db_id not in dbs_with_specific_config:
849
+ # Collect unique table names from all databases with the same id
850
+ unique_tables = list(set(db.session_table_name for db in dbs))
504
851
  session_config.dbs.append(
505
852
  DatabaseConfig(
506
853
  db_id=db_id,
507
854
  domain_config=SessionDomainConfig(display_name=db_id),
855
+ tables=unique_tables,
508
856
  )
509
857
  )
510
858
 
@@ -518,12 +866,15 @@ class AgentOS:
518
866
 
519
867
  dbs_with_specific_config = [db.db_id for db in memory_config.dbs]
520
868
 
521
- for db_id in self.dbs.keys():
869
+ for db_id, dbs in self.dbs.items():
522
870
  if db_id not in dbs_with_specific_config:
871
+ # Collect unique table names from all databases with the same id
872
+ unique_tables = list(set(db.memory_table_name for db in dbs))
523
873
  memory_config.dbs.append(
524
874
  DatabaseConfig(
525
875
  db_id=db_id,
526
876
  domain_config=MemoryDomainConfig(display_name=db_id),
877
+ tables=unique_tables,
527
878
  )
528
879
  )
529
880
 
@@ -557,12 +908,15 @@ class AgentOS:
557
908
 
558
909
  dbs_with_specific_config = [db.db_id for db in metrics_config.dbs]
559
910
 
560
- for db_id in self.dbs.keys():
911
+ for db_id, dbs in self.dbs.items():
561
912
  if db_id not in dbs_with_specific_config:
913
+ # Collect unique table names from all databases with the same id
914
+ unique_tables = list(set(db.metrics_table_name for db in dbs))
562
915
  metrics_config.dbs.append(
563
916
  DatabaseConfig(
564
917
  db_id=db_id,
565
918
  domain_config=MetricsDomainConfig(display_name=db_id),
919
+ tables=unique_tables,
566
920
  )
567
921
  )
568
922
 
@@ -576,17 +930,50 @@ class AgentOS:
576
930
 
577
931
  dbs_with_specific_config = [db.db_id for db in evals_config.dbs]
578
932
 
579
- for db_id in self.dbs.keys():
933
+ for db_id, dbs in self.dbs.items():
580
934
  if db_id not in dbs_with_specific_config:
935
+ # Collect unique table names from all databases with the same id
936
+ unique_tables = list(set(db.eval_table_name for db in dbs))
581
937
  evals_config.dbs.append(
582
938
  DatabaseConfig(
583
939
  db_id=db_id,
584
940
  domain_config=EvalsDomainConfig(display_name=db_id),
941
+ tables=unique_tables,
585
942
  )
586
943
  )
587
944
 
588
945
  return evals_config
589
946
 
947
+ def _get_traces_config(self) -> TracesConfig:
948
+ traces_config = self.config.traces if self.config and self.config.traces else TracesConfig()
949
+
950
+ if traces_config.dbs is None:
951
+ traces_config.dbs = []
952
+
953
+ dbs_with_specific_config = [db.db_id for db in traces_config.dbs]
954
+
955
+ # If tracing_db is explicitly set, only use that database for traces
956
+ if self.tracing_db is not None:
957
+ if self.tracing_db.id not in dbs_with_specific_config:
958
+ traces_config.dbs.append(
959
+ DatabaseConfig(
960
+ db_id=self.tracing_db.id,
961
+ domain_config=TracesDomainConfig(display_name=self.tracing_db.id),
962
+ )
963
+ )
964
+ else:
965
+ # Fall back to all discovered databases
966
+ for db_id in self.dbs.keys():
967
+ if db_id not in dbs_with_specific_config:
968
+ traces_config.dbs.append(
969
+ DatabaseConfig(
970
+ db_id=db_id,
971
+ domain_config=TracesDomainConfig(display_name=db_id),
972
+ )
973
+ )
974
+
975
+ return traces_config
976
+
590
977
  def serve(
591
978
  self,
592
979
  app: Union[str, FastAPI],
@@ -595,6 +982,7 @@ class AgentOS:
595
982
  port: int = 7777,
596
983
  reload: bool = False,
597
984
  workers: Optional[int] = None,
985
+ access_log: bool = False,
598
986
  **kwargs,
599
987
  ):
600
988
  import uvicorn
@@ -627,4 +1015,13 @@ class AgentOS:
627
1015
  )
628
1016
  )
629
1017
 
630
- uvicorn.run(app=app, host=host, port=port, reload=reload, workers=workers, **kwargs)
1018
+ uvicorn.run(
1019
+ app=app,
1020
+ host=host,
1021
+ port=port,
1022
+ reload=reload,
1023
+ workers=workers,
1024
+ access_log=access_log,
1025
+ lifespan="on",
1026
+ **kwargs,
1027
+ )