agno 2.0.0rc2__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. agno/agent/agent.py +6009 -2874
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +595 -187
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +3 -0
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +339 -266
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +1011 -566
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +110 -37
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +143 -4
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +60 -6
  142. agno/models/openai/chat.py +102 -43
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +81 -5
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -175
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +266 -112
  205. agno/run/base.py +53 -24
  206. agno/run/team.py +252 -111
  207. agno/run/workflow.py +156 -45
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1692
  213. agno/tools/brightdata.py +3 -3
  214. agno/tools/cartesia.py +3 -5
  215. agno/tools/dalle.py +9 -8
  216. agno/tools/decorator.py +4 -2
  217. agno/tools/desi_vocal.py +2 -2
  218. agno/tools/duckduckgo.py +15 -11
  219. agno/tools/e2b.py +20 -13
  220. agno/tools/eleven_labs.py +26 -28
  221. agno/tools/exa.py +21 -16
  222. agno/tools/fal.py +4 -4
  223. agno/tools/file.py +153 -23
  224. agno/tools/file_generation.py +350 -0
  225. agno/tools/firecrawl.py +4 -4
  226. agno/tools/function.py +257 -37
  227. agno/tools/giphy.py +2 -2
  228. agno/tools/gmail.py +238 -14
  229. agno/tools/google_drive.py +270 -0
  230. agno/tools/googlecalendar.py +36 -8
  231. agno/tools/googlesheets.py +20 -5
  232. agno/tools/jira.py +20 -0
  233. agno/tools/knowledge.py +3 -3
  234. agno/tools/lumalab.py +3 -3
  235. agno/tools/mcp/__init__.py +10 -0
  236. agno/tools/mcp/mcp.py +331 -0
  237. agno/tools/mcp/multi_mcp.py +347 -0
  238. agno/tools/mcp/params.py +24 -0
  239. agno/tools/mcp_toolbox.py +284 -0
  240. agno/tools/mem0.py +11 -17
  241. agno/tools/memori.py +1 -53
  242. agno/tools/memory.py +419 -0
  243. agno/tools/models/azure_openai.py +2 -2
  244. agno/tools/models/gemini.py +3 -3
  245. agno/tools/models/groq.py +3 -5
  246. agno/tools/models/nebius.py +7 -7
  247. agno/tools/models_labs.py +25 -15
  248. agno/tools/notion.py +204 -0
  249. agno/tools/openai.py +4 -9
  250. agno/tools/opencv.py +3 -3
  251. agno/tools/parallel.py +314 -0
  252. agno/tools/replicate.py +7 -7
  253. agno/tools/scrapegraph.py +58 -31
  254. agno/tools/searxng.py +2 -2
  255. agno/tools/serper.py +2 -2
  256. agno/tools/slack.py +18 -3
  257. agno/tools/spider.py +2 -2
  258. agno/tools/tavily.py +146 -0
  259. agno/tools/whatsapp.py +1 -1
  260. agno/tools/workflow.py +278 -0
  261. agno/tools/yfinance.py +12 -11
  262. agno/utils/agent.py +820 -0
  263. agno/utils/audio.py +27 -0
  264. agno/utils/common.py +90 -1
  265. agno/utils/events.py +222 -7
  266. agno/utils/gemini.py +181 -23
  267. agno/utils/hooks.py +57 -0
  268. agno/utils/http.py +111 -0
  269. agno/utils/knowledge.py +12 -5
  270. agno/utils/log.py +1 -0
  271. agno/utils/mcp.py +95 -5
  272. agno/utils/media.py +188 -10
  273. agno/utils/merge_dict.py +22 -1
  274. agno/utils/message.py +60 -0
  275. agno/utils/models/claude.py +40 -11
  276. agno/utils/models/cohere.py +1 -1
  277. agno/utils/models/watsonx.py +1 -1
  278. agno/utils/openai.py +1 -1
  279. agno/utils/print_response/agent.py +105 -21
  280. agno/utils/print_response/team.py +103 -38
  281. agno/utils/print_response/workflow.py +251 -34
  282. agno/utils/reasoning.py +22 -1
  283. agno/utils/serialize.py +32 -0
  284. agno/utils/streamlit.py +16 -10
  285. agno/utils/string.py +41 -0
  286. agno/utils/team.py +98 -9
  287. agno/utils/tools.py +1 -1
  288. agno/vectordb/base.py +23 -4
  289. agno/vectordb/cassandra/cassandra.py +65 -9
  290. agno/vectordb/chroma/chromadb.py +182 -38
  291. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  292. agno/vectordb/couchbase/couchbase.py +105 -10
  293. agno/vectordb/lancedb/lance_db.py +183 -135
  294. agno/vectordb/langchaindb/langchaindb.py +25 -7
  295. agno/vectordb/lightrag/lightrag.py +17 -3
  296. agno/vectordb/llamaindex/__init__.py +3 -0
  297. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  298. agno/vectordb/milvus/milvus.py +126 -9
  299. agno/vectordb/mongodb/__init__.py +7 -1
  300. agno/vectordb/mongodb/mongodb.py +112 -7
  301. agno/vectordb/pgvector/pgvector.py +142 -21
  302. agno/vectordb/pineconedb/pineconedb.py +80 -8
  303. agno/vectordb/qdrant/qdrant.py +125 -39
  304. agno/vectordb/redis/__init__.py +9 -0
  305. agno/vectordb/redis/redisdb.py +694 -0
  306. agno/vectordb/singlestore/singlestore.py +111 -25
  307. agno/vectordb/surrealdb/surrealdb.py +31 -5
  308. agno/vectordb/upstashdb/upstashdb.py +76 -8
  309. agno/vectordb/weaviate/weaviate.py +86 -15
  310. agno/workflow/__init__.py +2 -0
  311. agno/workflow/agent.py +299 -0
  312. agno/workflow/condition.py +112 -18
  313. agno/workflow/loop.py +69 -10
  314. agno/workflow/parallel.py +266 -118
  315. agno/workflow/router.py +110 -17
  316. agno/workflow/step.py +645 -136
  317. agno/workflow/steps.py +65 -6
  318. agno/workflow/types.py +71 -33
  319. agno/workflow/workflow.py +2113 -300
  320. agno-2.3.0.dist-info/METADATA +618 -0
  321. agno-2.3.0.dist-info/RECORD +577 -0
  322. agno-2.3.0.dist-info/licenses/LICENSE +201 -0
  323. agno/knowledge/reader/url_reader.py +0 -128
  324. agno/tools/googlesearch.py +0 -98
  325. agno/tools/mcp.py +0 -610
  326. agno/utils/models/aws_claude.py +0 -170
  327. agno-2.0.0rc2.dist-info/METADATA +0 -355
  328. agno-2.0.0rc2.dist-info/RECORD +0 -515
  329. agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
  330. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  331. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/os/app.py CHANGED
@@ -1,17 +1,19 @@
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, Optional, Union
4
+ from typing import Any, Dict, List, Literal, Optional, Tuple, Union
5
5
  from uuid import uuid4
6
6
 
7
- from fastapi import FastAPI, HTTPException
7
+ from fastapi import APIRouter, FastAPI, HTTPException
8
8
  from fastapi.responses import JSONResponse
9
+ from fastapi.routing import APIRoute
9
10
  from rich import box
10
11
  from rich.panel import Panel
11
- from starlette.middleware.cors import CORSMiddleware
12
12
  from starlette.requests import Request
13
13
 
14
14
  from agno.agent.agent import Agent
15
+ from agno.db.base import AsyncBaseDb, BaseDb
16
+ from agno.knowledge.knowledge import Knowledge
15
17
  from agno.os.config import (
16
18
  AgentOSConfig,
17
19
  DatabaseConfig,
@@ -27,21 +29,30 @@ from agno.os.config import (
27
29
  SessionDomainConfig,
28
30
  )
29
31
  from agno.os.interfaces.base import BaseInterface
30
- from agno.os.router import get_base_router
32
+ from agno.os.router import get_base_router, get_websocket_router
31
33
  from agno.os.routers.evals import get_eval_router
34
+ from agno.os.routers.health import get_health_router
35
+ from agno.os.routers.home import get_home_router
32
36
  from agno.os.routers.knowledge import get_knowledge_router
33
37
  from agno.os.routers.memory import get_memory_router
34
38
  from agno.os.routers.metrics import get_metrics_router
35
39
  from agno.os.routers.session import get_session_router
36
40
  from agno.os.settings import AgnoAPISettings
37
- from agno.os.utils import generate_id
41
+ from agno.os.utils import (
42
+ collect_mcp_tools_from_team,
43
+ collect_mcp_tools_from_workflow,
44
+ find_conflicting_routes,
45
+ load_yaml_config,
46
+ update_cors_middleware,
47
+ )
38
48
  from agno.team.team import Team
39
- from agno.tools.mcp import MCPTools, MultiMCPTools
49
+ from agno.utils.log import log_debug, log_error, log_warning
50
+ from agno.utils.string import generate_id, generate_id_from_name
40
51
  from agno.workflow.workflow import Workflow
41
52
 
42
53
 
43
54
  @asynccontextmanager
44
- async def mcp_lifespan(app, mcp_tools: List[Union[MCPTools, MultiMCPTools]]):
55
+ async def mcp_lifespan(_, mcp_tools):
45
56
  """Manage MCP connection lifecycle inside a FastAPI app"""
46
57
  # Startup logic: connect to all contextual MCP servers
47
58
  for tool in mcp_tools:
@@ -54,100 +65,223 @@ async def mcp_lifespan(app, mcp_tools: List[Union[MCPTools, MultiMCPTools]]):
54
65
  await tool.close()
55
66
 
56
67
 
68
+ def _combine_app_lifespans(lifespans: list) -> Any:
69
+ """Combine multiple FastAPI app lifespan context managers into one."""
70
+ if len(lifespans) == 1:
71
+ return lifespans[0]
72
+
73
+ from contextlib import asynccontextmanager
74
+
75
+ @asynccontextmanager
76
+ async def combined_lifespan(app):
77
+ async def _run_nested(index: int):
78
+ if index >= len(lifespans):
79
+ yield
80
+ return
81
+
82
+ async with lifespans[index](app):
83
+ async for _ in _run_nested(index + 1):
84
+ yield
85
+
86
+ async for _ in _run_nested(0):
87
+ yield
88
+
89
+ return combined_lifespan
90
+
91
+
57
92
  class AgentOS:
58
93
  def __init__(
59
94
  self,
60
- os_id: Optional[str] = None,
95
+ id: Optional[str] = None,
61
96
  name: Optional[str] = None,
62
97
  description: Optional[str] = None,
63
98
  version: Optional[str] = None,
64
99
  agents: Optional[List[Agent]] = None,
65
100
  teams: Optional[List[Team]] = None,
66
101
  workflows: Optional[List[Workflow]] = None,
102
+ knowledge: Optional[List[Knowledge]] = None,
67
103
  interfaces: Optional[List[BaseInterface]] = None,
104
+ a2a_interface: bool = False,
68
105
  config: Optional[Union[str, AgentOSConfig]] = None,
69
106
  settings: Optional[AgnoAPISettings] = None,
70
- fastapi_app: Optional[FastAPI] = None,
71
107
  lifespan: Optional[Any] = None,
72
- enable_mcp: bool = False,
108
+ enable_mcp_server: bool = False,
109
+ base_app: Optional[FastAPI] = None,
110
+ on_route_conflict: Literal["preserve_agentos", "preserve_base_app", "error"] = "preserve_agentos",
73
111
  telemetry: bool = True,
112
+ auto_provision_dbs: bool = True,
74
113
  ):
75
- if not agents and not workflows and not teams:
76
- raise ValueError("Either agents, teams or workflows must be provided.")
114
+ """Initialize AgentOS.
115
+
116
+ Args:
117
+ id: Unique identifier for this AgentOS instance
118
+ name: Name of the AgentOS instance
119
+ description: Description of the AgentOS instance
120
+ version: Version of the AgentOS instance
121
+ agents: List of agents to include in the OS
122
+ teams: List of teams to include in the OS
123
+ workflows: List of workflows to include in the OS
124
+ knowledge: List of knowledge bases to include in the OS
125
+ interfaces: List of interfaces to include in the OS
126
+ a2a_interface: Whether to expose the OS agents and teams in an A2A server
127
+ config: Configuration file path or AgentOSConfig instance
128
+ settings: API settings for the OS
129
+ lifespan: Optional lifespan context manager for the FastAPI app
130
+ enable_mcp_server: Whether to enable MCP (Model Context Protocol)
131
+ base_app: Optional base FastAPI app to use for the AgentOS. All routes and middleware will be added to this app.
132
+ on_route_conflict: What to do when a route conflict is detected in case a custom base_app is provided.
133
+ telemetry: Whether to enable telemetry
134
+
135
+ """
136
+ if not agents and not workflows and not teams and not knowledge:
137
+ raise ValueError("Either agents, teams, workflows or knowledge bases must be provided.")
77
138
 
78
- self.config = self._load_yaml_config(config) if isinstance(config, str) else config
139
+ self.config = load_yaml_config(config) if isinstance(config, str) else config
79
140
 
80
141
  self.agents: Optional[List[Agent]] = agents
81
142
  self.workflows: Optional[List[Workflow]] = workflows
82
143
  self.teams: Optional[List[Team]] = teams
83
144
  self.interfaces = interfaces or []
84
-
145
+ self.a2a_interface = a2a_interface
146
+ self.knowledge = knowledge
85
147
  self.settings: AgnoAPISettings = settings or AgnoAPISettings()
86
-
148
+ self.auto_provision_dbs = auto_provision_dbs
87
149
  self._app_set = False
88
- self.fastapi_app: Optional[FastAPI] = None
89
- if fastapi_app:
90
- self.fastapi_app = fastapi_app
150
+
151
+ if base_app:
152
+ self.base_app: Optional[FastAPI] = base_app
91
153
  self._app_set = True
154
+ self.on_route_conflict = on_route_conflict
155
+ else:
156
+ self.base_app = None
157
+ self._app_set = False
158
+ self.on_route_conflict = on_route_conflict
92
159
 
93
160
  self.interfaces = interfaces or []
94
161
 
95
- self.os_id: Optional[str] = os_id
96
162
  self.name = name
163
+
164
+ self.id = id
165
+ if not self.id:
166
+ self.id = generate_id(self.name) if self.name else str(uuid4())
167
+
97
168
  self.version = version
98
169
  self.description = description
99
170
 
100
171
  self.telemetry = telemetry
101
172
 
102
- self.enable_mcp = enable_mcp
173
+ self.enable_mcp_server = enable_mcp_server
103
174
  self.lifespan = lifespan
104
175
 
105
176
  # List of all MCP tools used inside the AgentOS
106
- self.mcp_tools: List[Union[MCPTools, MultiMCPTools]] = []
107
-
108
- if self.agents:
109
- for agent in self.agents:
110
- # Track all MCP tools to later handle their connection
111
- if agent.tools:
112
- for tool in agent.tools:
113
- if isinstance(tool, MCPTools) or isinstance(tool, MultiMCPTools):
114
- self.mcp_tools.append(tool)
177
+ self.mcp_tools: List[Any] = []
178
+ self._mcp_app: Optional[Any] = None
115
179
 
116
- agent.initialize_agent()
180
+ self._initialize_agents()
181
+ self._initialize_teams()
182
+ self._initialize_workflows()
117
183
 
118
- # Required for the built-in routes to work
119
- agent.store_events = True
120
-
121
- if self.teams:
122
- for team in self.teams:
123
- # Track all MCP tools to later handle their connection
124
- if team.tools:
125
- for tool in team.tools:
126
- if isinstance(tool, MCPTools) or isinstance(tool, MultiMCPTools):
127
- self.mcp_tools.append(tool)
184
+ if self.telemetry:
185
+ from agno.api.os import OSLaunch, log_os_telemetry
128
186
 
129
- team.initialize_team()
187
+ log_os_telemetry(launch=OSLaunch(os_id=self.id, data=self._get_telemetry_data()))
130
188
 
131
- # Required for the built-in routes to work
132
- team.store_events = True
189
+ def _add_agent_os_to_lifespan_function(self, lifespan):
190
+ """
191
+ Inspect a lifespan function and wrap it to pass agent_os if it accepts it.
133
192
 
134
- for member in team.members:
135
- if isinstance(member, Agent):
136
- member.team_id = None
137
- member.initialize_agent()
138
- elif isinstance(member, Team):
139
- member.initialize_team()
193
+ Returns:
194
+ A wrapped lifespan that passes agent_os if the lifespan function expects it.
195
+ """
196
+ # Getting the actual function inside the lifespan
197
+ lifespan_function = lifespan
198
+ if hasattr(lifespan, "__wrapped__"):
199
+ lifespan_function = lifespan.__wrapped__
140
200
 
141
- if self.workflows:
142
- for workflow in self.workflows:
143
- # TODO: track MCP tools in workflow members
144
- if not workflow.id:
145
- workflow.id = generate_id(workflow.name)
201
+ try:
202
+ from inspect import signature
146
203
 
147
- if self.telemetry:
148
- from agno.api.os import OSLaunch, log_os_telemetry
204
+ # Inspecting the lifespan function signature to find its parameters
205
+ sig = signature(lifespan_function)
206
+ params = list(sig.parameters.keys())
207
+
208
+ # If the lifespan function expects the 'agent_os' parameter, add it
209
+ if "agent_os" in params:
210
+ return partial(lifespan, agent_os=self)
211
+ else:
212
+ return lifespan
213
+
214
+ except (ValueError, TypeError):
215
+ return lifespan
216
+
217
+ def resync(self, app: FastAPI) -> None:
218
+ """Resync the AgentOS to discover, initialize and configure: agents, teams, workflows, databases and knowledge bases."""
219
+ self._initialize_agents()
220
+ self._initialize_teams()
221
+ self._initialize_workflows()
222
+ self._auto_discover_databases()
223
+ self._auto_discover_knowledge_instances()
224
+
225
+ if self.enable_mcp_server:
226
+ from agno.os.mcp import get_mcp_server
227
+
228
+ self._mcp_app = get_mcp_server(self)
149
229
 
150
- log_os_telemetry(launch=OSLaunch(os_id=self.os_id, data=self._get_telemetry_data()))
230
+ self._reprovision_routers(app=app)
231
+
232
+ def _reprovision_routers(self, app: FastAPI) -> None:
233
+ """Re-provision all routes for the AgentOS."""
234
+ updated_routers = [
235
+ get_session_router(dbs=self.dbs),
236
+ get_metrics_router(dbs=self.dbs),
237
+ get_knowledge_router(knowledge_instances=self.knowledge_instances),
238
+ get_memory_router(dbs=self.dbs),
239
+ get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
240
+ ]
241
+
242
+ # Clear all previously existing routes
243
+ app.router.routes = [
244
+ route
245
+ for route in app.router.routes
246
+ if hasattr(route, "path")
247
+ and route.path in ["/docs", "/redoc", "/openapi.json", "/docs/oauth2-redirect"]
248
+ or route.path.startswith("/mcp") # type: ignore
249
+ ]
250
+
251
+ # Add the built-in routes
252
+ self._add_built_in_routes(app=app)
253
+
254
+ # Add the updated routes
255
+ for router in updated_routers:
256
+ self._add_router(app, router)
257
+
258
+ # Mount MCP if needed
259
+ if self.enable_mcp_server and self._mcp_app:
260
+ app.mount("/", self._mcp_app)
261
+
262
+ def _add_built_in_routes(self, app: FastAPI) -> None:
263
+ """Add all AgentOSbuilt-in routes to the given app."""
264
+ # Add the home router if MCP server is not enabled
265
+ if not self.enable_mcp_server:
266
+ self._add_router(app, get_home_router(self))
267
+
268
+ self._add_router(app, get_health_router(health_endpoint="/health"))
269
+ self._add_router(app, get_base_router(self, settings=self.settings))
270
+ self._add_router(app, get_websocket_router(self, settings=self.settings))
271
+
272
+ # Add A2A interface if relevant
273
+ has_a2a_interface = False
274
+ for interface in self.interfaces:
275
+ if not has_a2a_interface and interface.__class__.__name__ == "A2A":
276
+ has_a2a_interface = True
277
+ interface_router = interface.get_router()
278
+ self._add_router(app, interface_router)
279
+ if self.a2a_interface and not has_a2a_interface:
280
+ from agno.os.interfaces.a2a import A2A
281
+
282
+ a2a_interface = A2A(agents=self.agents, teams=self.teams, workflows=self.workflows)
283
+ self.interfaces.append(a2a_interface)
284
+ self._add_router(app, a2a_interface.get_router())
151
285
 
152
286
  def _make_app(self, lifespan: Optional[Any] = None) -> FastAPI:
153
287
  # Adjust the FastAPI app lifespan to handle MCP connections if relevant
@@ -164,7 +298,7 @@ class AgentOS:
164
298
  async with mcp_tools_lifespan(app): # type: ignore
165
299
  yield
166
300
 
167
- app_lifespan = combined_lifespan # type: ignore
301
+ app_lifespan = combined_lifespan
168
302
  else:
169
303
  app_lifespan = mcp_tools_lifespan
170
304
 
@@ -178,77 +312,175 @@ class AgentOS:
178
312
  lifespan=app_lifespan,
179
313
  )
180
314
 
315
+ def _initialize_agents(self) -> None:
316
+ """Initialize and configure all agents for AgentOS usage."""
317
+ if not self.agents:
318
+ return
319
+
320
+ for agent in self.agents:
321
+ # Track all MCP tools to later handle their connection
322
+ if agent.tools:
323
+ for tool in agent.tools:
324
+ # Checking if the tool is a MCPTools or MultiMCPTools instance
325
+ type_name = type(tool).__name__
326
+ if type_name in ("MCPTools", "MultiMCPTools"):
327
+ if tool not in self.mcp_tools:
328
+ self.mcp_tools.append(tool)
329
+
330
+ agent.initialize_agent()
331
+
332
+ # Required for the built-in routes to work
333
+ agent.store_events = True
334
+
335
+ def _initialize_teams(self) -> None:
336
+ """Initialize and configure all teams for AgentOS usage."""
337
+ if not self.teams:
338
+ return
339
+
340
+ for team in self.teams:
341
+ # Track all MCP tools recursively
342
+ collect_mcp_tools_from_team(team, self.mcp_tools)
343
+
344
+ team.initialize_team()
345
+
346
+ for member in team.members:
347
+ if isinstance(member, Agent):
348
+ member.team_id = None
349
+ member.initialize_agent()
350
+ elif isinstance(member, Team):
351
+ member.initialize_team()
352
+
353
+ # Required for the built-in routes to work
354
+ team.store_events = True
355
+
356
+ def _initialize_workflows(self) -> None:
357
+ """Initialize and configure all workflows for AgentOS usage."""
358
+ if not self.workflows:
359
+ return
360
+
361
+ if self.workflows:
362
+ for workflow in self.workflows:
363
+ # Track MCP tools recursively in workflow members
364
+ collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
365
+
366
+ if not workflow.id:
367
+ workflow.id = generate_id_from_name(workflow.name)
368
+
369
+ # Required for the built-in routes to work
370
+ workflow.store_events = True
371
+
181
372
  def get_app(self) -> FastAPI:
182
- if not self.fastapi_app:
183
- if self.enable_mcp:
373
+ if self.base_app:
374
+ fastapi_app = self.base_app
375
+
376
+ # Initialize MCP server if enabled
377
+ if self.enable_mcp_server:
378
+ from agno.os.mcp import get_mcp_server
379
+
380
+ self._mcp_app = get_mcp_server(self)
381
+
382
+ # Collect all lifespans that need to be combined
383
+ lifespans = []
384
+
385
+ # The user provided lifespan
386
+ if self.lifespan:
387
+ # Wrap the user lifespan with agent_os parameter
388
+ wrapped_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
389
+ lifespans.append(wrapped_lifespan)
390
+
391
+ # The provided app's existing lifespan
392
+ if fastapi_app.router.lifespan_context:
393
+ lifespans.append(fastapi_app.router.lifespan_context)
394
+
395
+ # The MCP tools lifespan
396
+ if self.mcp_tools:
397
+ lifespans.append(partial(mcp_lifespan, mcp_tools=self.mcp_tools))
398
+
399
+ # The /mcp server lifespan
400
+ if self.enable_mcp_server and self._mcp_app:
401
+ lifespans.append(self._mcp_app.lifespan)
402
+
403
+ # Combine lifespans and set them in the app
404
+ if lifespans:
405
+ fastapi_app.router.lifespan_context = _combine_app_lifespans(lifespans)
406
+
407
+ else:
408
+ if self.enable_mcp_server:
184
409
  from contextlib import asynccontextmanager
185
410
 
186
411
  from agno.os.mcp import get_mcp_server
187
412
 
188
- self.mcp_app = get_mcp_server(self)
413
+ self._mcp_app = get_mcp_server(self)
189
414
 
190
- final_lifespan = self.mcp_app.lifespan
415
+ final_lifespan = self._mcp_app.lifespan # type: ignore
191
416
  if self.lifespan is not None:
417
+ # Wrap the user lifespan with agent_os parameter
418
+ wrapped_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
419
+
192
420
  # Combine both lifespans
193
421
  @asynccontextmanager
194
422
  async def combined_lifespan(app: FastAPI):
195
423
  # Run both lifespans
196
- async with self.lifespan(app): # type: ignore
197
- async with self.mcp_app.lifespan(app): # type: ignore
424
+ async with wrapped_lifespan(app): # type: ignore
425
+ async with self._mcp_app.lifespan(app): # type: ignore
198
426
  yield
199
427
 
200
428
  final_lifespan = combined_lifespan # type: ignore
201
429
 
202
- self.fastapi_app = self._make_app(lifespan=final_lifespan)
430
+ fastapi_app = self._make_app(lifespan=final_lifespan)
203
431
  else:
204
- self.fastapi_app = self._make_app(lifespan=self.lifespan)
432
+ # Wrap the user lifespan with agent_os parameter
433
+ wrapped_user_lifespan = None
434
+ if self.lifespan is not None:
435
+ wrapped_user_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
205
436
 
206
- # Add routes
207
- self.fastapi_app.include_router(get_base_router(self, settings=self.settings))
437
+ fastapi_app = self._make_app(lifespan=wrapped_user_lifespan)
208
438
 
209
- for interface in self.interfaces:
210
- interface_router = interface.get_router()
211
- self.fastapi_app.include_router(interface_router)
439
+ self._add_built_in_routes(app=fastapi_app)
212
440
 
213
441
  self._auto_discover_databases()
214
442
  self._auto_discover_knowledge_instances()
215
- self._setup_routers()
443
+
444
+ routers = [
445
+ get_session_router(dbs=self.dbs),
446
+ get_memory_router(dbs=self.dbs),
447
+ get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
448
+ get_metrics_router(dbs=self.dbs),
449
+ get_knowledge_router(knowledge_instances=self.knowledge_instances),
450
+ ]
451
+
452
+ for router in routers:
453
+ self._add_router(fastapi_app, router)
216
454
 
217
455
  # Mount MCP if needed
218
- if self.enable_mcp and self.mcp_app:
219
- self.fastapi_app.mount("/", self.mcp_app)
456
+ if self.enable_mcp_server and self._mcp_app:
457
+ fastapi_app.mount("/", self._mcp_app)
220
458
 
221
- # Add middleware (only if app is not set)
222
459
  if not self._app_set:
223
460
 
224
- @self.fastapi_app.exception_handler(HTTPException)
225
- async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
461
+ @fastapi_app.exception_handler(HTTPException)
462
+ async def http_exception_handler(_, exc: HTTPException) -> JSONResponse:
463
+ log_error(f"HTTP exception: {exc.status_code} {exc.detail}")
226
464
  return JSONResponse(
227
465
  status_code=exc.status_code,
228
466
  content={"detail": str(exc.detail)},
229
467
  )
230
468
 
231
- async def general_exception_handler(request: Request, call_next):
232
- try:
233
- return await call_next(request)
234
- except Exception as e:
235
- return JSONResponse(
236
- status_code=e.status_code if hasattr(e, "status_code") else 500, # type: ignore
237
- content={"detail": str(e)},
238
- )
469
+ @fastapi_app.exception_handler(Exception)
470
+ async def general_exception_handler(_: Request, exc: Exception) -> JSONResponse:
471
+ import traceback
239
472
 
240
- self.fastapi_app.middleware("http")(general_exception_handler)
473
+ log_error(f"Unhandled exception:\n{traceback.format_exc(limit=5)}")
241
474
 
242
- self.fastapi_app.add_middleware(
243
- CORSMiddleware,
244
- allow_origins=self.settings.cors_origin_list, # type: ignore
245
- allow_credentials=True,
246
- allow_methods=["*"],
247
- allow_headers=["*"],
248
- expose_headers=["*"],
249
- )
475
+ return JSONResponse(
476
+ status_code=getattr(exc, "status_code", 500),
477
+ content={"detail": str(exc)},
478
+ )
479
+
480
+ # Update CORS middleware
481
+ update_cors_middleware(fastapi_app, self.settings.cors_origin_list) # type: ignore
250
482
 
251
- return self.fastapi_app
483
+ return fastapi_app
252
484
 
253
485
  def get_routes(self) -> List[Any]:
254
486
  """Retrieve all routes from the FastAPI app.
@@ -260,6 +492,62 @@ class AgentOS:
260
492
 
261
493
  return app.routes
262
494
 
495
+ def _add_router(self, fastapi_app: FastAPI, router: APIRouter) -> None:
496
+ """Add a router to the FastAPI app, avoiding route conflicts.
497
+
498
+ Args:
499
+ router: The APIRouter to add
500
+ """
501
+
502
+ conflicts = find_conflicting_routes(fastapi_app, router)
503
+ conflicting_routes = [conflict["route"] for conflict in conflicts]
504
+
505
+ if conflicts and self._app_set:
506
+ if self.on_route_conflict == "preserve_base_app":
507
+ # Skip conflicting AgentOS routes, prefer user's existing routes
508
+ for conflict in conflicts:
509
+ methods_str = ", ".join(conflict["methods"]) # type: ignore
510
+ log_debug(
511
+ f"Skipping conflicting AgentOS route: {methods_str} {conflict['path']} - "
512
+ f"Using existing custom route instead"
513
+ )
514
+
515
+ # Create a new router without the conflicting routes
516
+ filtered_router = APIRouter()
517
+ for route in router.routes:
518
+ if route not in conflicting_routes:
519
+ filtered_router.routes.append(route)
520
+
521
+ # Use the filtered router if it has any routes left
522
+ if filtered_router.routes:
523
+ fastapi_app.include_router(filtered_router)
524
+
525
+ elif self.on_route_conflict == "preserve_agentos":
526
+ # Log warnings but still add all routes (AgentOS routes will override)
527
+ for conflict in conflicts:
528
+ methods_str = ", ".join(conflict["methods"]) # type: ignore
529
+ log_warning(
530
+ f"Route conflict detected: {methods_str} {conflict['path']} - "
531
+ f"AgentOS route will override existing custom route"
532
+ )
533
+
534
+ # Remove conflicting routes
535
+ for route in fastapi_app.routes:
536
+ for conflict in conflicts:
537
+ if isinstance(route, APIRoute):
538
+ if route.path == conflict["path"] and list(route.methods) == list(conflict["methods"]): # type: ignore
539
+ fastapi_app.routes.pop(fastapi_app.routes.index(route))
540
+
541
+ fastapi_app.include_router(router)
542
+
543
+ elif self.on_route_conflict == "error":
544
+ conflicting_paths = [conflict["path"] for conflict in conflicts]
545
+ raise ValueError(f"Route conflict detected: {conflicting_paths}")
546
+
547
+ else:
548
+ # No conflicts, add router normally
549
+ fastapi_app.include_router(router)
550
+
263
551
  def _get_telemetry_data(self) -> Dict[str, Any]:
264
552
  """Get the telemetry data for the OS"""
265
553
  return {
@@ -269,59 +557,160 @@ class AgentOS:
269
557
  "interfaces": [interface.type for interface in self.interfaces] if self.interfaces else None,
270
558
  }
271
559
 
272
- def _load_yaml_config(self, config_file_path: str) -> AgentOSConfig:
273
- """Load a YAML config file and return the configuration as an AgentOSConfig instance."""
274
- from pathlib import Path
275
-
276
- import yaml
277
-
278
- # Validate that the path points to a YAML file
279
- path = Path(config_file_path)
280
- if path.suffix.lower() not in [".yaml", ".yml"]:
281
- raise ValueError(f"Config file must have a .yaml or .yml extension, got: {config_file_path}")
282
-
283
- # Load the YAML file
284
- with open(config_file_path, "r") as f:
285
- return AgentOSConfig.model_validate(yaml.safe_load(f))
286
-
287
560
  def _auto_discover_databases(self) -> None:
288
- """Auto-discover the databases used by all contextual agents, teams and workflows."""
289
- dbs = {}
561
+ """Auto-discover and initialize the databases used by all contextual agents, teams and workflows."""
562
+
563
+ dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb]]] = {}
564
+ knowledge_dbs: Dict[
565
+ str, List[Union[BaseDb, AsyncBaseDb]]
566
+ ] = {} # Track databases specifically used for knowledge
290
567
 
291
568
  for agent in self.agents or []:
292
569
  if agent.db:
293
- dbs[agent.db.id] = agent.db
570
+ self._register_db_with_validation(dbs, agent.db)
294
571
  if agent.knowledge and agent.knowledge.contents_db:
295
- dbs[agent.knowledge.contents_db.id] = agent.knowledge.contents_db
572
+ self._register_db_with_validation(knowledge_dbs, agent.knowledge.contents_db)
296
573
 
297
574
  for team in self.teams or []:
298
575
  if team.db:
299
- dbs[team.db.id] = team.db
576
+ self._register_db_with_validation(dbs, team.db)
300
577
  if team.knowledge and team.knowledge.contents_db:
301
- dbs[team.knowledge.contents_db.id] = team.knowledge.contents_db
578
+ self._register_db_with_validation(knowledge_dbs, team.knowledge.contents_db)
302
579
 
303
580
  for workflow in self.workflows or []:
304
581
  if workflow.db:
305
- dbs[workflow.db.id] = workflow.db
582
+ self._register_db_with_validation(dbs, workflow.db)
583
+
584
+ for knowledge_base in self.knowledge or []:
585
+ if knowledge_base.contents_db:
586
+ self._register_db_with_validation(knowledge_dbs, knowledge_base.contents_db)
306
587
 
307
588
  for interface in self.interfaces or []:
308
589
  if interface.agent and interface.agent.db:
309
- dbs[interface.agent.db.id] = interface.agent.db
590
+ self._register_db_with_validation(dbs, interface.agent.db)
310
591
  elif interface.team and interface.team.db:
311
- dbs[interface.team.db.id] = interface.team.db
592
+ self._register_db_with_validation(dbs, interface.team.db)
312
593
 
313
594
  self.dbs = dbs
595
+ self.knowledge_dbs = knowledge_dbs
596
+
597
+ # Initialize/scaffold all discovered databases
598
+ if self.auto_provision_dbs:
599
+ import asyncio
600
+ import concurrent.futures
601
+
602
+ try:
603
+ # If we're already in an event loop, run in a separate thread
604
+ asyncio.get_running_loop()
605
+
606
+ def run_in_new_loop():
607
+ new_loop = asyncio.new_event_loop()
608
+ asyncio.set_event_loop(new_loop)
609
+ try:
610
+ return new_loop.run_until_complete(self._initialize_databases())
611
+ finally:
612
+ new_loop.close()
613
+
614
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
615
+ future = executor.submit(run_in_new_loop)
616
+ future.result() # Wait for completion
617
+
618
+ except RuntimeError:
619
+ # No event loop running, use asyncio.run
620
+ asyncio.run(self._initialize_databases())
621
+
622
+ async def _initialize_databases(self) -> None:
623
+ """Initialize all discovered databases and create all Agno tables that don't exist yet."""
624
+ from itertools import chain
625
+
626
+ # Collect all database instances and remove duplicates by identity
627
+ unique_dbs = list(
628
+ {
629
+ id(db): db
630
+ for db in chain(
631
+ chain.from_iterable(self.dbs.values()), chain.from_iterable(self.knowledge_dbs.values())
632
+ )
633
+ }.values()
634
+ )
635
+
636
+ # Separate sync and async databases
637
+ sync_dbs: List[Tuple[str, BaseDb]] = []
638
+ async_dbs: List[Tuple[str, AsyncBaseDb]] = []
639
+
640
+ for db in unique_dbs:
641
+ target = async_dbs if isinstance(db, AsyncBaseDb) else sync_dbs
642
+ target.append((db.id, db)) # type: ignore
643
+
644
+ # Initialize sync databases
645
+ for db_id, db in sync_dbs:
646
+ try:
647
+ if hasattr(db, "_create_all_tables") and callable(getattr(db, "_create_all_tables")):
648
+ db._create_all_tables()
649
+ else:
650
+ log_debug(f"No table initialization needed for {db.__class__.__name__}")
651
+
652
+ except Exception as e:
653
+ log_warning(f"Failed to initialize {db.__class__.__name__} (id: {db_id}): {e}")
654
+
655
+ # Initialize async databases
656
+ for db_id, db in async_dbs:
657
+ try:
658
+ log_debug(f"Initializing async {db.__class__.__name__} (id: {db_id})")
659
+
660
+ if hasattr(db, "_create_all_tables") and callable(getattr(db, "_create_all_tables")):
661
+ await db._create_all_tables()
662
+ else:
663
+ log_debug(f"No table initialization needed for async {db.__class__.__name__}")
664
+
665
+ except Exception as e:
666
+ log_warning(f"Failed to initialize async database {db.__class__.__name__} (id: {db_id}): {e}")
667
+
668
+ def _get_db_table_names(self, db: BaseDb) -> Dict[str, str]:
669
+ """Get the table names for a database"""
670
+ table_names = {
671
+ "session_table_name": db.session_table_name,
672
+ "culture_table_name": db.culture_table_name,
673
+ "memory_table_name": db.memory_table_name,
674
+ "metrics_table_name": db.metrics_table_name,
675
+ "evals_table_name": db.eval_table_name,
676
+ "knowledge_table_name": db.knowledge_table_name,
677
+ }
678
+ return {k: v for k, v in table_names.items() if v is not None}
679
+
680
+ def _register_db_with_validation(
681
+ self, registered_dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb]]], db: Union[BaseDb, AsyncBaseDb]
682
+ ) -> None:
683
+ """Register a database in the contextual OS after validating it is not conflicting with registered databases"""
684
+ if db.id in registered_dbs:
685
+ registered_dbs[db.id].append(db)
686
+ else:
687
+ registered_dbs[db.id] = [db]
314
688
 
315
689
  def _auto_discover_knowledge_instances(self) -> None:
316
690
  """Auto-discover the knowledge instances used by all contextual agents, teams and workflows."""
317
- knowledge_instances = []
691
+ seen_ids = set()
692
+ knowledge_instances: List[Knowledge] = []
693
+
694
+ def _add_knowledge_if_not_duplicate(knowledge: "Knowledge") -> None:
695
+ """Add knowledge instance if it's not already in the list (by object identity or db_id)."""
696
+ # Use database ID if available, otherwise use object ID as fallback
697
+ if not knowledge.contents_db:
698
+ return
699
+ if knowledge.contents_db.id in seen_ids:
700
+ return
701
+ seen_ids.add(knowledge.contents_db.id)
702
+ knowledge_instances.append(knowledge)
703
+
318
704
  for agent in self.agents or []:
319
705
  if agent.knowledge:
320
- knowledge_instances.append(agent.knowledge)
706
+ _add_knowledge_if_not_duplicate(agent.knowledge)
321
707
 
322
708
  for team in self.teams or []:
323
709
  if team.knowledge:
324
- knowledge_instances.append(team.knowledge)
710
+ _add_knowledge_if_not_duplicate(team.knowledge)
711
+
712
+ for knowledge_base in self.knowledge or []:
713
+ _add_knowledge_if_not_duplicate(knowledge_base)
325
714
 
326
715
  self.knowledge_instances = knowledge_instances
327
716
 
@@ -331,17 +720,16 @@ class AgentOS:
331
720
  if session_config.dbs is None:
332
721
  session_config.dbs = []
333
722
 
334
- multiple_dbs: bool = len(self.dbs.keys()) > 1
335
723
  dbs_with_specific_config = [db.db_id for db in session_config.dbs]
336
-
337
- for db_id in self.dbs.keys():
724
+ for db_id, dbs in self.dbs.items():
338
725
  if db_id not in dbs_with_specific_config:
726
+ # Collect unique table names from all databases with the same id
727
+ unique_tables = list(set(db.session_table_name for db in dbs))
339
728
  session_config.dbs.append(
340
729
  DatabaseConfig(
341
730
  db_id=db_id,
342
- domain_config=SessionDomainConfig(
343
- display_name="Sessions" if not multiple_dbs else "Sessions in database '" + db_id + "'"
344
- ),
731
+ domain_config=SessionDomainConfig(display_name=db_id),
732
+ tables=unique_tables,
345
733
  )
346
734
  )
347
735
 
@@ -353,17 +741,17 @@ class AgentOS:
353
741
  if memory_config.dbs is None:
354
742
  memory_config.dbs = []
355
743
 
356
- multiple_dbs: bool = len(self.dbs.keys()) > 1
357
744
  dbs_with_specific_config = [db.db_id for db in memory_config.dbs]
358
745
 
359
- for db_id in self.dbs.keys():
746
+ for db_id, dbs in self.dbs.items():
360
747
  if db_id not in dbs_with_specific_config:
748
+ # Collect unique table names from all databases with the same id
749
+ unique_tables = list(set(db.memory_table_name for db in dbs))
361
750
  memory_config.dbs.append(
362
751
  DatabaseConfig(
363
752
  db_id=db_id,
364
- domain_config=MemoryDomainConfig(
365
- display_name="Memory" if not multiple_dbs else "Memory in database '" + db_id + "'"
366
- ),
753
+ domain_config=MemoryDomainConfig(display_name=db_id),
754
+ tables=unique_tables,
367
755
  )
368
756
  )
369
757
 
@@ -375,17 +763,15 @@ class AgentOS:
375
763
  if knowledge_config.dbs is None:
376
764
  knowledge_config.dbs = []
377
765
 
378
- multiple_dbs: bool = len(self.dbs.keys()) > 1
379
766
  dbs_with_specific_config = [db.db_id for db in knowledge_config.dbs]
380
767
 
381
- for db_id in self.dbs.keys():
768
+ # Only add databases that are actually used for knowledge contents
769
+ for db_id in self.knowledge_dbs.keys():
382
770
  if db_id not in dbs_with_specific_config:
383
771
  knowledge_config.dbs.append(
384
772
  DatabaseConfig(
385
773
  db_id=db_id,
386
- domain_config=KnowledgeDomainConfig(
387
- display_name="Knowledge" if not multiple_dbs else "Knowledge in database " + db_id
388
- ),
774
+ domain_config=KnowledgeDomainConfig(display_name=db_id),
389
775
  )
390
776
  )
391
777
 
@@ -397,17 +783,17 @@ class AgentOS:
397
783
  if metrics_config.dbs is None:
398
784
  metrics_config.dbs = []
399
785
 
400
- multiple_dbs: bool = len(self.dbs.keys()) > 1
401
786
  dbs_with_specific_config = [db.db_id for db in metrics_config.dbs]
402
787
 
403
- for db_id in self.dbs.keys():
788
+ for db_id, dbs in self.dbs.items():
404
789
  if db_id not in dbs_with_specific_config:
790
+ # Collect unique table names from all databases with the same id
791
+ unique_tables = list(set(db.metrics_table_name for db in dbs))
405
792
  metrics_config.dbs.append(
406
793
  DatabaseConfig(
407
794
  db_id=db_id,
408
- domain_config=MetricsDomainConfig(
409
- display_name="Metrics" if not multiple_dbs else "Metrics in database '" + db_id + "'"
410
- ),
795
+ domain_config=MetricsDomainConfig(display_name=db_id),
796
+ tables=unique_tables,
411
797
  )
412
798
  )
413
799
 
@@ -419,45 +805,22 @@ class AgentOS:
419
805
  if evals_config.dbs is None:
420
806
  evals_config.dbs = []
421
807
 
422
- multiple_dbs: bool = len(self.dbs.keys()) > 1
423
808
  dbs_with_specific_config = [db.db_id for db in evals_config.dbs]
424
809
 
425
- for db_id in self.dbs.keys():
810
+ for db_id, dbs in self.dbs.items():
426
811
  if db_id not in dbs_with_specific_config:
812
+ # Collect unique table names from all databases with the same id
813
+ unique_tables = list(set(db.eval_table_name for db in dbs))
427
814
  evals_config.dbs.append(
428
815
  DatabaseConfig(
429
816
  db_id=db_id,
430
- domain_config=EvalsDomainConfig(
431
- display_name="Evals" if not multiple_dbs else "Evals in database '" + db_id + "'"
432
- ),
817
+ domain_config=EvalsDomainConfig(display_name=db_id),
818
+ tables=unique_tables,
433
819
  )
434
820
  )
435
821
 
436
822
  return evals_config
437
823
 
438
- def _setup_routers(self) -> None:
439
- """Add all routers to the FastAPI app."""
440
- if not self.dbs or not self.fastapi_app:
441
- return
442
-
443
- routers = [
444
- get_session_router(dbs=self.dbs),
445
- get_memory_router(dbs=self.dbs),
446
- get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
447
- get_metrics_router(dbs=self.dbs),
448
- get_knowledge_router(knowledge_instances=self.knowledge_instances),
449
- ]
450
-
451
- for router in routers:
452
- self.fastapi_app.include_router(router)
453
-
454
- def set_os_id(self) -> str:
455
- # If os_id is already set, keep it instead of overriding with UUID
456
- if self.os_id is None:
457
- self.os_id = str(uuid4())
458
-
459
- return self.os_id
460
-
461
824
  def serve(
462
825
  self,
463
826
  app: Union[str, FastAPI],
@@ -466,6 +829,7 @@ class AgentOS:
466
829
  port: int = 7777,
467
830
  reload: bool = False,
468
831
  workers: Optional[int] = None,
832
+ access_log: bool = False,
469
833
  **kwargs,
470
834
  ):
471
835
  import uvicorn
@@ -479,13 +843,17 @@ class AgentOS:
479
843
  from rich.align import Align
480
844
  from rich.console import Console, Group
481
845
 
482
- aligned_endpoint = Align.center(f"[bold cyan]{public_endpoint}[/bold cyan]")
483
- connection_endpoint = f"\n\n[bold dark_orange]Running on:[/bold dark_orange] http://{host}:{port}"
846
+ panel_group = [
847
+ Align.center(f"[bold cyan]{public_endpoint}[/bold cyan]"),
848
+ Align.center(f"\n\n[bold dark_orange]OS running on:[/bold dark_orange] http://{host}:{port}"),
849
+ ]
850
+ if bool(self.settings.os_security_key):
851
+ panel_group.append(Align.center("\n\n[bold chartreuse3]:lock: Security Enabled[/bold chartreuse3]"))
484
852
 
485
853
  console = Console()
486
854
  console.print(
487
855
  Panel(
488
- Group(aligned_endpoint, connection_endpoint),
856
+ Group(*panel_group),
489
857
  title="AgentOS",
490
858
  expand=False,
491
859
  border_style="dark_orange",
@@ -494,4 +862,4 @@ class AgentOS:
494
862
  )
495
863
  )
496
864
 
497
- uvicorn.run(app=app, host=host, port=port, reload=reload, workers=workers, **kwargs)
865
+ uvicorn.run(app=app, host=host, port=port, reload=reload, workers=workers, access_log=access_log, **kwargs)