letta-nightly 0.7.29.dev20250602104315__py3-none-any.whl → 0.8.0.dev20250604104349__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 (138) hide show
  1. letta/__init__.py +7 -1
  2. letta/agent.py +16 -9
  3. letta/agents/base_agent.py +1 -0
  4. letta/agents/ephemeral_summary_agent.py +104 -0
  5. letta/agents/helpers.py +35 -3
  6. letta/agents/letta_agent.py +492 -176
  7. letta/agents/letta_agent_batch.py +22 -16
  8. letta/agents/prompts/summary_system_prompt.txt +62 -0
  9. letta/agents/voice_agent.py +22 -7
  10. letta/agents/voice_sleeptime_agent.py +13 -8
  11. letta/constants.py +33 -1
  12. letta/data_sources/connectors.py +52 -36
  13. letta/errors.py +4 -0
  14. letta/functions/ast_parsers.py +13 -30
  15. letta/functions/function_sets/base.py +3 -1
  16. letta/functions/functions.py +2 -0
  17. letta/functions/mcp_client/base_client.py +151 -97
  18. letta/functions/mcp_client/sse_client.py +49 -31
  19. letta/functions/mcp_client/stdio_client.py +107 -106
  20. letta/functions/schema_generator.py +22 -22
  21. letta/groups/helpers.py +3 -4
  22. letta/groups/sleeptime_multi_agent.py +4 -4
  23. letta/groups/sleeptime_multi_agent_v2.py +22 -0
  24. letta/helpers/composio_helpers.py +16 -0
  25. letta/helpers/converters.py +20 -0
  26. letta/helpers/datetime_helpers.py +1 -6
  27. letta/helpers/tool_rule_solver.py +2 -1
  28. letta/interfaces/anthropic_streaming_interface.py +17 -2
  29. letta/interfaces/openai_chat_completions_streaming_interface.py +1 -0
  30. letta/interfaces/openai_streaming_interface.py +18 -2
  31. letta/jobs/llm_batch_job_polling.py +1 -1
  32. letta/jobs/scheduler.py +1 -1
  33. letta/llm_api/anthropic_client.py +24 -3
  34. letta/llm_api/google_ai_client.py +0 -15
  35. letta/llm_api/google_vertex_client.py +6 -5
  36. letta/llm_api/llm_client_base.py +15 -0
  37. letta/llm_api/openai.py +2 -2
  38. letta/llm_api/openai_client.py +60 -8
  39. letta/orm/__init__.py +2 -0
  40. letta/orm/agent.py +45 -43
  41. letta/orm/base.py +0 -2
  42. letta/orm/block.py +1 -0
  43. letta/orm/custom_columns.py +13 -0
  44. letta/orm/enums.py +5 -0
  45. letta/orm/file.py +3 -1
  46. letta/orm/files_agents.py +68 -0
  47. letta/orm/mcp_server.py +48 -0
  48. letta/orm/message.py +1 -0
  49. letta/orm/organization.py +11 -2
  50. letta/orm/passage.py +25 -10
  51. letta/orm/sandbox_config.py +5 -2
  52. letta/orm/sqlalchemy_base.py +171 -110
  53. letta/prompts/system/memgpt_base.txt +6 -1
  54. letta/prompts/system/memgpt_v2_chat.txt +57 -0
  55. letta/prompts/system/sleeptime.txt +2 -0
  56. letta/prompts/system/sleeptime_v2.txt +28 -0
  57. letta/schemas/agent.py +87 -20
  58. letta/schemas/block.py +7 -1
  59. letta/schemas/file.py +57 -0
  60. letta/schemas/mcp.py +74 -0
  61. letta/schemas/memory.py +5 -2
  62. letta/schemas/message.py +9 -0
  63. letta/schemas/openai/openai.py +0 -6
  64. letta/schemas/providers.py +33 -4
  65. letta/schemas/tool.py +26 -21
  66. letta/schemas/tool_execution_result.py +5 -0
  67. letta/server/db.py +23 -8
  68. letta/server/rest_api/app.py +73 -56
  69. letta/server/rest_api/interface.py +4 -4
  70. letta/server/rest_api/routers/v1/agents.py +132 -47
  71. letta/server/rest_api/routers/v1/blocks.py +3 -2
  72. letta/server/rest_api/routers/v1/embeddings.py +3 -3
  73. letta/server/rest_api/routers/v1/groups.py +3 -3
  74. letta/server/rest_api/routers/v1/jobs.py +14 -17
  75. letta/server/rest_api/routers/v1/organizations.py +10 -10
  76. letta/server/rest_api/routers/v1/providers.py +12 -10
  77. letta/server/rest_api/routers/v1/runs.py +3 -3
  78. letta/server/rest_api/routers/v1/sandbox_configs.py +12 -12
  79. letta/server/rest_api/routers/v1/sources.py +108 -43
  80. letta/server/rest_api/routers/v1/steps.py +8 -6
  81. letta/server/rest_api/routers/v1/tools.py +134 -95
  82. letta/server/rest_api/utils.py +12 -1
  83. letta/server/server.py +272 -73
  84. letta/services/agent_manager.py +246 -313
  85. letta/services/block_manager.py +30 -9
  86. letta/services/context_window_calculator/__init__.py +0 -0
  87. letta/services/context_window_calculator/context_window_calculator.py +150 -0
  88. letta/services/context_window_calculator/token_counter.py +82 -0
  89. letta/services/file_processor/__init__.py +0 -0
  90. letta/services/file_processor/chunker/__init__.py +0 -0
  91. letta/services/file_processor/chunker/llama_index_chunker.py +29 -0
  92. letta/services/file_processor/embedder/__init__.py +0 -0
  93. letta/services/file_processor/embedder/openai_embedder.py +84 -0
  94. letta/services/file_processor/file_processor.py +123 -0
  95. letta/services/file_processor/parser/__init__.py +0 -0
  96. letta/services/file_processor/parser/base_parser.py +9 -0
  97. letta/services/file_processor/parser/mistral_parser.py +54 -0
  98. letta/services/file_processor/types.py +0 -0
  99. letta/services/files_agents_manager.py +184 -0
  100. letta/services/group_manager.py +118 -0
  101. letta/services/helpers/agent_manager_helper.py +76 -21
  102. letta/services/helpers/tool_execution_helper.py +3 -0
  103. letta/services/helpers/tool_parser_helper.py +100 -0
  104. letta/services/identity_manager.py +44 -42
  105. letta/services/job_manager.py +21 -10
  106. letta/services/mcp/base_client.py +5 -2
  107. letta/services/mcp/sse_client.py +3 -5
  108. letta/services/mcp/stdio_client.py +3 -5
  109. letta/services/mcp_manager.py +281 -0
  110. letta/services/message_manager.py +40 -26
  111. letta/services/organization_manager.py +55 -19
  112. letta/services/passage_manager.py +211 -13
  113. letta/services/provider_manager.py +48 -2
  114. letta/services/sandbox_config_manager.py +105 -0
  115. letta/services/source_manager.py +4 -5
  116. letta/services/step_manager.py +9 -6
  117. letta/services/summarizer/summarizer.py +50 -23
  118. letta/services/telemetry_manager.py +7 -0
  119. letta/services/tool_executor/tool_execution_manager.py +11 -52
  120. letta/services/tool_executor/tool_execution_sandbox.py +4 -34
  121. letta/services/tool_executor/tool_executor.py +107 -105
  122. letta/services/tool_manager.py +56 -17
  123. letta/services/tool_sandbox/base.py +39 -92
  124. letta/services/tool_sandbox/e2b_sandbox.py +16 -11
  125. letta/services/tool_sandbox/local_sandbox.py +51 -23
  126. letta/services/user_manager.py +36 -3
  127. letta/settings.py +10 -3
  128. letta/templates/__init__.py +0 -0
  129. letta/templates/sandbox_code_file.py.j2 +47 -0
  130. letta/templates/template_helper.py +16 -0
  131. letta/tracing.py +30 -1
  132. letta/types/__init__.py +7 -0
  133. letta/utils.py +25 -1
  134. {letta_nightly-0.7.29.dev20250602104315.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/METADATA +7 -2
  135. {letta_nightly-0.7.29.dev20250602104315.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/RECORD +138 -112
  136. {letta_nightly-0.7.29.dev20250602104315.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/LICENSE +0 -0
  137. {letta_nightly-0.7.29.dev20250602104315.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/WHEEL +0 -0
  138. {letta_nightly-0.7.29.dev20250602104315.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/entry_points.txt +0 -0
letta/server/server.py CHANGED
@@ -19,13 +19,11 @@ import letta.constants as constants
19
19
  import letta.server.utils as server_utils
20
20
  import letta.system as system
21
21
  from letta.agent import Agent, save_agent
22
+ from letta.agents.letta_agent import LettaAgent
22
23
  from letta.config import LettaConfig
23
- from letta.constants import LETTA_TOOL_EXECUTION_DIR
24
+ from letta.constants import CORE_MEMORY_SOURCE_CHAR_LIMIT, LETTA_TOOL_EXECUTION_DIR
24
25
  from letta.data_sources.connectors import DataConnector, load_data
25
26
  from letta.errors import HandleNotFoundError
26
- from letta.functions.mcp_client.base_client import BaseMCPClient
27
- from letta.functions.mcp_client.sse_client import MCP_CONFIG_TOPLEVEL_KEY, SSEMCPClient
28
- from letta.functions.mcp_client.stdio_client import StdioMCPClient
29
27
  from letta.functions.mcp_client.types import MCPServerType, MCPTool, SSEServerConfig, StdioServerConfig
30
28
  from letta.groups.helpers import load_multi_agent
31
29
  from letta.helpers.datetime_helpers import get_utc_time
@@ -52,7 +50,6 @@ from letta.schemas.letta_response import LettaResponse
52
50
  from letta.schemas.llm_config import LLMConfig
53
51
  from letta.schemas.memory import ArchivalMemorySummary, Memory, RecallMemorySummary
54
52
  from letta.schemas.message import Message, MessageCreate, MessageUpdate
55
- from letta.schemas.organization import Organization
56
53
  from letta.schemas.passage import Passage, PassageUpdate
57
54
  from letta.schemas.providers import (
58
55
  AnthropicBedrockProvider,
@@ -82,11 +79,16 @@ from letta.server.rest_api.interface import StreamingServerInterface
82
79
  from letta.server.rest_api.utils import sse_async_generator
83
80
  from letta.services.agent_manager import AgentManager
84
81
  from letta.services.block_manager import BlockManager
82
+ from letta.services.files_agents_manager import FileAgentManager
85
83
  from letta.services.group_manager import GroupManager
86
84
  from letta.services.helpers.tool_execution_helper import prepare_local_sandbox
87
85
  from letta.services.identity_manager import IdentityManager
88
86
  from letta.services.job_manager import JobManager
89
87
  from letta.services.llm_batch_manager import LLMBatchManager
88
+ from letta.services.mcp.base_client import AsyncBaseMCPClient
89
+ from letta.services.mcp.sse_client import MCP_CONFIG_TOPLEVEL_KEY, AsyncSSEMCPClient
90
+ from letta.services.mcp.stdio_client import AsyncStdioMCPClient
91
+ from letta.services.mcp_manager import MCPManager
90
92
  from letta.services.message_manager import MessageManager
91
93
  from letta.services.organization_manager import OrganizationManager
92
94
  from letta.services.passage_manager import PassageManager
@@ -94,8 +96,8 @@ from letta.services.provider_manager import ProviderManager
94
96
  from letta.services.sandbox_config_manager import SandboxConfigManager
95
97
  from letta.services.source_manager import SourceManager
96
98
  from letta.services.step_manager import StepManager
97
- from letta.services.telemetry_manager import TelemetryManager
98
- from letta.services.tool_executor.tool_execution_sandbox import ToolExecutionSandbox
99
+ from letta.services.telemetry_manager import NoopTelemetryManager, TelemetryManager
100
+ from letta.services.tool_executor.tool_execution_manager import ToolExecutionManager
99
101
  from letta.services.tool_manager import ToolManager
100
102
  from letta.services.user_manager import UserManager
101
103
  from letta.settings import model_settings, settings, tool_settings
@@ -203,6 +205,7 @@ class SyncServer(Server):
203
205
  self.passage_manager = PassageManager()
204
206
  self.user_manager = UserManager()
205
207
  self.tool_manager = ToolManager()
208
+ self.mcp_manager = MCPManager()
206
209
  self.block_manager = BlockManager()
207
210
  self.source_manager = SourceManager()
208
211
  self.sandbox_config_manager = SandboxConfigManager()
@@ -215,6 +218,7 @@ class SyncServer(Server):
215
218
  self.group_manager = GroupManager()
216
219
  self.batch_manager = LLMBatchManager()
217
220
  self.telemetry_manager = TelemetryManager()
221
+ self.file_agent_manager = FileAgentManager()
218
222
 
219
223
  # A resusable httpx client
220
224
  timeout = httpx.Timeout(connect=10.0, read=20.0, write=10.0, pool=10.0)
@@ -380,20 +384,31 @@ class SyncServer(Server):
380
384
  self._enabled_providers.append(XAIProvider(name="xai", api_key=model_settings.xai_api_key))
381
385
 
382
386
  # For MCP
387
+ # TODO: remove this
383
388
  """Initialize the MCP clients (there may be multiple)"""
389
+ self.mcp_clients: Dict[str, AsyncBaseMCPClient] = {}
390
+
391
+ # TODO: Remove these in memory caches
392
+ self._llm_config_cache = {}
393
+ self._embedding_config_cache = {}
394
+
395
+ # TODO: Replace this with the Anthropic client we have in house
396
+ self.anthropic_async_client = AsyncAnthropic()
397
+
398
+ async def init_mcp_clients(self):
399
+ # TODO: remove this
384
400
  mcp_server_configs = self.get_mcp_servers()
385
- self.mcp_clients: Dict[str, BaseMCPClient] = {}
386
401
 
387
402
  for server_name, server_config in mcp_server_configs.items():
388
403
  if server_config.type == MCPServerType.SSE:
389
- self.mcp_clients[server_name] = SSEMCPClient(server_config)
404
+ self.mcp_clients[server_name] = AsyncSSEMCPClient(server_config)
390
405
  elif server_config.type == MCPServerType.STDIO:
391
- self.mcp_clients[server_name] = StdioMCPClient(server_config)
406
+ self.mcp_clients[server_name] = AsyncStdioMCPClient(server_config)
392
407
  else:
393
408
  raise ValueError(f"Invalid MCP server config: {server_config}")
394
409
 
395
410
  try:
396
- self.mcp_clients[server_name].connect_to_server()
411
+ await self.mcp_clients[server_name].connect_to_server()
397
412
  except Exception as e:
398
413
  logger.error(e)
399
414
  self.mcp_clients.pop(server_name)
@@ -401,17 +416,10 @@ class SyncServer(Server):
401
416
  # Print out the tools that are connected
402
417
  for server_name, client in self.mcp_clients.items():
403
418
  logger.info(f"Attempting to fetch tools from MCP server: {server_name}")
404
- mcp_tools = client.list_tools()
419
+ mcp_tools = await client.list_tools()
405
420
  logger.info(f"MCP tools connected: {', '.join([t.name for t in mcp_tools])}")
406
421
  logger.debug(f"MCP tools: {', '.join([str(t) for t in mcp_tools])}")
407
422
 
408
- # TODO: Remove these in memory caches
409
- self._llm_config_cache = {}
410
- self._embedding_config_cache = {}
411
-
412
- # TODO: Replace this with the Anthropic client we have in house
413
- self.anthropic_async_client = AsyncAnthropic()
414
-
415
423
  def load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
416
424
  """Updated method to load agents from persisted storage"""
417
425
  agent_state = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
@@ -820,7 +828,10 @@ class SyncServer(Server):
820
828
  ) -> AgentState:
821
829
  if request.llm_config is None:
822
830
  if request.model is None:
823
- raise ValueError("Must specify either model or llm_config in request")
831
+ if settings.default_llm_handle is None:
832
+ raise ValueError("Must specify either model or llm_config in request")
833
+ else:
834
+ request.model = settings.default_llm_handle
824
835
  config_params = {
825
836
  "handle": request.model,
826
837
  "context_window_limit": request.context_window_limit,
@@ -834,7 +845,10 @@ class SyncServer(Server):
834
845
 
835
846
  if request.embedding_config is None:
836
847
  if request.embedding is None:
837
- raise ValueError("Must specify either embedding or embedding_config in request")
848
+ if settings.default_embedding_handle is None:
849
+ raise ValueError("Must specify either embedding or embedding_config in request")
850
+ else:
851
+ request.embedding = settings.default_embedding_handle
838
852
  embedding_config_params = {
839
853
  "handle": request.embedding,
840
854
  "embedding_chunk_size": request.embedding_chunk_size or constants.DEFAULT_EMBEDDING_CHUNK_SIZE,
@@ -852,9 +866,9 @@ class SyncServer(Server):
852
866
 
853
867
  if request.enable_sleeptime:
854
868
  if request.agent_type == AgentType.voice_convo_agent:
855
- main_agent = self.create_voice_sleeptime_agent(main_agent=main_agent, actor=actor)
869
+ main_agent = await self.create_voice_sleeptime_agent_async(main_agent=main_agent, actor=actor)
856
870
  else:
857
- main_agent = self.create_sleeptime_agent(main_agent=main_agent, actor=actor)
871
+ main_agent = await self.create_sleeptime_agent_async(main_agent=main_agent, actor=actor)
858
872
 
859
873
  return main_agent
860
874
 
@@ -897,12 +911,12 @@ class SyncServer(Server):
897
911
  request.embedding_config = await self.get_embedding_config_from_handle_async(handle=request.embedding, actor=actor)
898
912
 
899
913
  if request.enable_sleeptime:
900
- agent = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
914
+ agent = await self.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
901
915
  if agent.multi_agent_group is None:
902
916
  if agent.agent_type == AgentType.voice_convo_agent:
903
- self.create_voice_sleeptime_agent(main_agent=agent, actor=actor)
917
+ await self.create_voice_sleeptime_agent_async(main_agent=agent, actor=actor)
904
918
  else:
905
- self.create_sleeptime_agent(main_agent=agent, actor=actor)
919
+ await self.create_sleeptime_agent_async(main_agent=agent, actor=actor)
906
920
 
907
921
  return await self.agent_manager.update_agent_async(
908
922
  agent_id=agent_id,
@@ -942,6 +956,38 @@ class SyncServer(Server):
942
956
  )
943
957
  return self.agent_manager.get_agent_by_id(agent_id=main_agent.id, actor=actor)
944
958
 
959
+ async def create_sleeptime_agent_async(self, main_agent: AgentState, actor: User) -> AgentState:
960
+ request = CreateAgent(
961
+ name=main_agent.name + "-sleeptime",
962
+ agent_type=AgentType.sleeptime_agent,
963
+ block_ids=[block.id for block in main_agent.memory.blocks],
964
+ memory_blocks=[
965
+ CreateBlock(
966
+ label="memory_persona",
967
+ value=get_persona_text("sleeptime_memory_persona"),
968
+ ),
969
+ ],
970
+ llm_config=main_agent.llm_config,
971
+ embedding_config=main_agent.embedding_config,
972
+ project_id=main_agent.project_id,
973
+ )
974
+ sleeptime_agent = await self.agent_manager.create_agent_async(
975
+ agent_create=request,
976
+ actor=actor,
977
+ )
978
+ await self.group_manager.create_group_async(
979
+ group=GroupCreate(
980
+ description="",
981
+ agent_ids=[sleeptime_agent.id],
982
+ manager_config=SleeptimeManager(
983
+ manager_agent_id=main_agent.id,
984
+ sleeptime_agent_frequency=5,
985
+ ),
986
+ ),
987
+ actor=actor,
988
+ )
989
+ return await self.agent_manager.get_agent_by_id_async(agent_id=main_agent.id, actor=actor)
990
+
945
991
  def create_voice_sleeptime_agent(self, main_agent: AgentState, actor: User) -> AgentState:
946
992
  # TODO: Inject system
947
993
  request = CreateAgent(
@@ -976,6 +1022,40 @@ class SyncServer(Server):
976
1022
  )
977
1023
  return self.agent_manager.get_agent_by_id(agent_id=main_agent.id, actor=actor)
978
1024
 
1025
+ async def create_voice_sleeptime_agent_async(self, main_agent: AgentState, actor: User) -> AgentState:
1026
+ # TODO: Inject system
1027
+ request = CreateAgent(
1028
+ name=main_agent.name + "-sleeptime",
1029
+ agent_type=AgentType.voice_sleeptime_agent,
1030
+ block_ids=[block.id for block in main_agent.memory.blocks],
1031
+ memory_blocks=[
1032
+ CreateBlock(
1033
+ label="memory_persona",
1034
+ value=get_persona_text("voice_memory_persona"),
1035
+ ),
1036
+ ],
1037
+ llm_config=LLMConfig.default_config("gpt-4.1"),
1038
+ embedding_config=main_agent.embedding_config,
1039
+ project_id=main_agent.project_id,
1040
+ )
1041
+ voice_sleeptime_agent = await self.agent_manager.create_agent_async(
1042
+ agent_create=request,
1043
+ actor=actor,
1044
+ )
1045
+ await self.group_manager.create_group_async(
1046
+ group=GroupCreate(
1047
+ description="Low latency voice chat with async memory management.",
1048
+ agent_ids=[voice_sleeptime_agent.id],
1049
+ manager_config=VoiceSleeptimeManager(
1050
+ manager_agent_id=main_agent.id,
1051
+ max_message_buffer_length=constants.DEFAULT_MAX_MESSAGE_BUFFER_LENGTH,
1052
+ min_message_buffer_length=constants.DEFAULT_MIN_MESSAGE_BUFFER_LENGTH,
1053
+ ),
1054
+ ),
1055
+ actor=actor,
1056
+ )
1057
+ return await self.agent_manager.get_agent_by_id_async(agent_id=main_agent.id, actor=actor)
1058
+
979
1059
  # convert name->id
980
1060
 
981
1061
  # TODO: These can be moved to agent_manager
@@ -1057,6 +1137,20 @@ class SyncServer(Server):
1057
1137
 
1058
1138
  return passages
1059
1139
 
1140
+ async def insert_archival_memory_async(self, agent_id: str, memory_contents: str, actor: User) -> List[Passage]:
1141
+ # Get the agent object (loaded in memory)
1142
+ agent_state = await self.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
1143
+ # Insert into archival memory
1144
+ # TODO: @mindy look at moving this to agent_manager to avoid above extra call
1145
+ passages = await self.passage_manager.insert_passage_async(
1146
+ agent_state=agent_state, agent_id=agent_id, text=memory_contents, actor=actor
1147
+ )
1148
+
1149
+ # rebuild agent system prompt - force since no archival change
1150
+ await self.agent_manager.rebuild_system_prompt_async(agent_id=agent_id, actor=actor, force=True)
1151
+
1152
+ return passages
1153
+
1060
1154
  def modify_archival_memory(self, agent_id: str, memory_id: str, passage: PassageUpdate, actor: User) -> List[Passage]:
1061
1155
  passage = Passage(**passage.model_dump(exclude_unset=True, exclude_none=True))
1062
1156
  passages = self.passage_manager.update_passage_by_id(passage_id=memory_id, passage=passage, actor=actor)
@@ -1073,6 +1167,17 @@ class SyncServer(Server):
1073
1167
  # rebuild system prompt and force
1074
1168
  self.agent_manager.rebuild_system_prompt(agent_id=passage.agent_id, actor=actor, force=True)
1075
1169
 
1170
+ async def delete_archival_memory_async(self, memory_id: str, actor: User):
1171
+ # TODO check if it exists first, and throw error if not
1172
+ # TODO: need to also rebuild the prompt here
1173
+ passage = await self.passage_manager.get_passage_by_id_async(passage_id=memory_id, actor=actor)
1174
+
1175
+ # delete the passage
1176
+ await self.passage_manager.delete_passage_by_id_async(passage_id=memory_id, actor=actor)
1177
+
1178
+ # rebuild system prompt and force
1179
+ await self.agent_manager.rebuild_system_prompt_async(agent_id=passage.agent_id, actor=actor, force=True)
1180
+
1076
1181
  def get_agent_recall(
1077
1182
  self,
1078
1183
  user_id: str,
@@ -1193,20 +1298,17 @@ class SyncServer(Server):
1193
1298
  await self.source_manager.delete_source(source_id=source_id, actor=actor)
1194
1299
 
1195
1300
  # delete data from passage store
1196
- # TODO: make async
1197
- passages_to_be_deleted = self.agent_manager.list_passages(actor=actor, source_id=source_id, limit=None)
1198
-
1199
- # TODO: make this async
1200
- self.passage_manager.delete_passages(actor=actor, passages=passages_to_be_deleted)
1301
+ passages_to_be_deleted = await self.agent_manager.list_passages_async(actor=actor, source_id=source_id, limit=None)
1302
+ await self.passage_manager.delete_source_passages_async(actor=actor, passages=passages_to_be_deleted)
1201
1303
 
1202
1304
  # TODO: delete data from agent passage stores (?)
1203
1305
 
1204
1306
  async def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: User) -> Job:
1205
1307
 
1206
1308
  # update job
1207
- job = self.job_manager.get_job_by_id(job_id, actor=actor)
1309
+ job = await self.job_manager.get_job_by_id_async(job_id, actor=actor)
1208
1310
  job.status = JobStatus.running
1209
- self.job_manager.update_job_by_id(job_id=job_id, job_update=JobUpdate(**job.model_dump()), actor=actor)
1311
+ await self.job_manager.update_job_by_id_async(job_id=job_id, job_update=JobUpdate(**job.model_dump()), actor=actor)
1210
1312
 
1211
1313
  # try:
1212
1314
  from letta.data_sources.connectors import DirectoryConnector
@@ -1225,43 +1327,137 @@ class SyncServer(Server):
1225
1327
 
1226
1328
  # Attach source to agent
1227
1329
  curr_passage_size = await self.agent_manager.passage_size_async(actor=actor, agent_id=agent_id)
1228
- agent_state = self.agent_manager.attach_source(agent_id=agent_state.id, source_id=source_id, actor=actor)
1330
+ agent_state = await self.agent_manager.attach_source_async(agent_id=agent_state.id, source_id=source_id, actor=actor)
1229
1331
  new_passage_size = await self.agent_manager.passage_size_async(actor=actor, agent_id=agent_id)
1230
1332
  assert new_passage_size >= curr_passage_size # in case empty files are added
1231
1333
 
1232
1334
  # rebuild system prompt and force
1233
- agent_state = self.agent_manager.rebuild_system_prompt(agent_id=agent_id, actor=actor, force=True)
1335
+ agent_state = await self.agent_manager.rebuild_system_prompt_async(agent_id=agent_id, actor=actor, force=True)
1234
1336
 
1235
1337
  # update job status
1236
1338
  job.status = JobStatus.completed
1237
1339
  job.metadata["num_passages"] = num_passages
1238
1340
  job.metadata["num_documents"] = num_documents
1239
- self.job_manager.update_job_by_id(job_id=job_id, job_update=JobUpdate(**job.model_dump()), actor=actor)
1341
+ await self.job_manager.update_job_by_id_async(job_id=job_id, job_update=JobUpdate(**job.model_dump()), actor=actor)
1240
1342
 
1241
1343
  return job
1242
1344
 
1243
- def sleeptime_document_ingest(self, main_agent: AgentState, source: Source, actor: User, clear_history: bool = False) -> None:
1244
- sleeptime_agent = self.create_document_sleeptime_agent(main_agent, source, actor, clear_history)
1245
- agent = self.load_agent(agent_id=sleeptime_agent.id, actor=actor)
1246
- for passage in self.list_data_source_passages(source_id=source.id, user_id=actor.id):
1247
- agent.step(
1345
+ async def load_file_to_source_via_mistral(self):
1346
+ pass
1347
+
1348
+ async def sleeptime_document_ingest_async(
1349
+ self, main_agent: AgentState, source: Source, actor: User, clear_history: bool = False
1350
+ ) -> None:
1351
+ sleeptime_agent_state = await self.create_document_sleeptime_agent_async(main_agent, source, actor, clear_history)
1352
+ sleeptime_agent = LettaAgent(
1353
+ agent_id=sleeptime_agent_state.id,
1354
+ message_manager=self.message_manager,
1355
+ agent_manager=self.agent_manager,
1356
+ block_manager=self.block_manager,
1357
+ passage_manager=self.passage_manager,
1358
+ actor=actor,
1359
+ step_manager=self.step_manager,
1360
+ telemetry_manager=self.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
1361
+ )
1362
+ passages = await self.agent_manager.list_passages_async(actor=actor, source_id=source.id)
1363
+ for passage in passages:
1364
+ await sleeptime_agent.step(
1248
1365
  input_messages=[
1249
1366
  MessageCreate(role="user", content=passage.text),
1250
1367
  ]
1251
1368
  )
1252
- self.agent_manager.delete_agent(agent_id=sleeptime_agent.id, actor=actor)
1369
+ await self.agent_manager.delete_agent_async(agent_id=sleeptime_agent_state.id, actor=actor)
1370
+
1371
+ async def _upsert_file_to_agent(self, agent_id: str, text: str, file_id: str, actor: User) -> None:
1372
+ """
1373
+ Internal method to create or update a file <-> agent association
1374
+ """
1375
+ truncated_text = text[:CORE_MEMORY_SOURCE_CHAR_LIMIT]
1376
+ await self.file_agent_manager.attach_file(agent_id=agent_id, file_id=file_id, actor=actor, visible_content=truncated_text)
1377
+
1378
+ async def _remove_file_from_agent(self, agent_id: str, file_id: str, actor: User) -> None:
1379
+ """
1380
+ Internal method to remove a document block for an agent.
1381
+ """
1382
+ try:
1383
+ await self.file_agent_manager.detach_file(
1384
+ agent_id=agent_id,
1385
+ file_id=file_id,
1386
+ actor=actor,
1387
+ )
1388
+ except NoResultFound:
1389
+ logger.info(f"File {file_id} already removed from agent {agent_id}, skipping...")
1390
+
1391
+ async def insert_file_into_context_windows(
1392
+ self, source_id: str, text: str, file_id: str, actor: User, agent_states: Optional[List[AgentState]] = None
1393
+ ) -> List[AgentState]:
1394
+ """
1395
+ Insert the uploaded document into the context window of all agents
1396
+ attached to the given source.
1397
+ """
1398
+ agent_states = agent_states or await self.source_manager.list_attached_agents(source_id=source_id, actor=actor)
1399
+
1400
+ # Return early
1401
+ if not agent_states:
1402
+ return []
1403
+
1404
+ logger.info(f"Inserting document into context window for source: {source_id}")
1405
+ logger.info(f"Attached agents: {[a.id for a in agent_states]}")
1406
+
1407
+ await asyncio.gather(*(self._upsert_file_to_agent(agent_state.id, text, file_id, actor) for agent_state in agent_states))
1408
+
1409
+ return agent_states
1410
+
1411
+ async def insert_files_into_context_window(self, agent_state: AgentState, texts: List[str], file_ids: List[str], actor: User) -> None:
1412
+ """
1413
+ Insert the uploaded documents into the context window of an agent
1414
+ attached to the given source.
1415
+ """
1416
+ logger.info(f"Inserting documents into context window for agent_state: {agent_state.id}")
1417
+
1418
+ if len(texts) != len(file_ids):
1419
+ raise ValueError(f"Mismatch between number of texts ({len(texts)}) and file ids ({len(file_ids)})")
1420
+
1421
+ await asyncio.gather(*(self._upsert_file_to_agent(agent_state.id, text, file_id, actor) for text, file_id in zip(texts, file_ids)))
1422
+
1423
+ async def remove_file_from_context_windows(self, source_id: str, file_id: str, actor: User) -> None:
1424
+ """
1425
+ Remove the document from the context window of all agents
1426
+ attached to the given source.
1427
+ """
1428
+ # TODO: We probably do NOT need to get the entire agent state, we can just get the IDs
1429
+ agent_states = await self.source_manager.list_attached_agents(source_id=source_id, actor=actor)
1430
+
1431
+ # Return early
1432
+ if not agent_states:
1433
+ return
1434
+
1435
+ logger.info(f"Removing file from context window for source: {source_id}")
1436
+ logger.info(f"Attached agents: {[a.id for a in agent_states]}")
1437
+
1438
+ await asyncio.gather(*(self._remove_file_from_agent(agent_state.id, file_id, actor) for agent_state in agent_states))
1439
+
1440
+ async def remove_files_from_context_window(self, agent_state: AgentState, file_ids: List[str], actor: User) -> None:
1441
+ """
1442
+ Remove multiple documents from the context window of an agent
1443
+ attached to the given source.
1444
+ """
1445
+ logger.info(f"Removing files from context window for agent_state: {agent_state.id}")
1446
+ logger.info(f"Files to remove: {file_ids}")
1253
1447
 
1254
- def create_document_sleeptime_agent(
1448
+ await asyncio.gather(*(self._remove_file_from_agent(agent_state.id, file_id, actor) for file_id in file_ids))
1449
+
1450
+ async def create_document_sleeptime_agent_async(
1255
1451
  self, main_agent: AgentState, source: Source, actor: User, clear_history: bool = False
1256
1452
  ) -> AgentState:
1257
1453
  try:
1258
- block = self.agent_manager.get_block_with_label(agent_id=main_agent.id, block_label=source.name, actor=actor)
1454
+ block = await self.agent_manager.get_block_with_label_async(agent_id=main_agent.id, block_label=source.name, actor=actor)
1259
1455
  except:
1260
- block = self.block_manager.create_or_update_block(Block(label=source.name, value=""), actor=actor)
1261
- self.agent_manager.attach_block(agent_id=main_agent.id, block_id=block.id, actor=actor)
1456
+ block = await self.block_manager.create_or_update_block_async(Block(label=source.name, value=""), actor=actor)
1457
+ await self.agent_manager.attach_block_async(agent_id=main_agent.id, block_id=block.id, actor=actor)
1262
1458
 
1263
1459
  if clear_history and block.value != "":
1264
- block = self.block_manager.update_block(block_id=block.id, block=BlockUpdate(value=""))
1460
+ block = await self.block_manager.update_block_async(block_id=block.id, block=BlockUpdate(value=""))
1265
1461
 
1266
1462
  request = CreateAgent(
1267
1463
  name=main_agent.name + "-doc-sleeptime",
@@ -1284,7 +1480,7 @@ class SyncServer(Server):
1284
1480
  include_base_tools=False,
1285
1481
  tools=constants.BASE_SLEEPTIME_TOOLS,
1286
1482
  )
1287
- return self.agent_manager.create_agent(
1483
+ return await self.agent_manager.create_agent_async(
1288
1484
  agent_create=request,
1289
1485
  actor=actor,
1290
1486
  )
@@ -1299,13 +1495,13 @@ class SyncServer(Server):
1299
1495
  # TODO: this should be implemented as a batch job or at least async, since it may take a long time
1300
1496
 
1301
1497
  # load data from a data source into the document store
1302
- user = self.user_manager.get_user_by_id(user_id=user_id)
1303
- source = await self.source_manager.get_source_by_name(source_name=source_name, actor=user)
1498
+ actor = await self.user_manager.get_actor_by_id_async(actor_id=user_id)
1499
+ source = await self.source_manager.get_source_by_name(source_name=source_name, actor=actor)
1304
1500
  if source is None:
1305
1501
  raise ValueError(f"Data source {source_name} does not exist for user {user_id}")
1306
1502
 
1307
1503
  # load data into the document store
1308
- passage_count, document_count = await load_data(connector, source, self.passage_manager, self.source_manager, actor=user)
1504
+ passage_count, document_count = await load_data(connector, source, self.passage_manager, self.source_manager, actor=actor)
1309
1505
  return passage_count, document_count
1310
1506
 
1311
1507
  def list_data_source_passages(self, user_id: str, source_id: str) -> List[Passage]:
@@ -1352,16 +1548,6 @@ class SyncServer(Server):
1352
1548
  # Get the current message
1353
1549
  return self.message_manager.update_message_by_id(message_id=message_id, message_update=request, actor=actor)
1354
1550
 
1355
- def get_organization_or_default(self, org_id: Optional[str]) -> Organization:
1356
- """Get the organization object for org_id if it exists, otherwise return the default organization object"""
1357
- if org_id is None:
1358
- org_id = self.organization_manager.DEFAULT_ORG_ID
1359
-
1360
- try:
1361
- return self.organization_manager.get_organization_by_id(org_id=org_id)
1362
- except NoResultFound:
1363
- raise HTTPException(status_code=404, detail=f"Organization with id {org_id} not found")
1364
-
1365
1551
  def list_llm_models(
1366
1552
  self,
1367
1553
  actor: User,
@@ -1407,7 +1593,7 @@ class SyncServer(Server):
1407
1593
  actor=actor,
1408
1594
  )
1409
1595
 
1410
- async def get_provider_models(provider):
1596
+ async def get_provider_models(provider: Provider) -> list[LLMConfig]:
1411
1597
  try:
1412
1598
  return await provider.list_llm_models_async()
1413
1599
  except Exception as e:
@@ -1763,7 +1949,7 @@ class SyncServer(Server):
1763
1949
  def add_embedding_model(self, request: EmbeddingConfig) -> EmbeddingConfig:
1764
1950
  """Add a new embedding model"""
1765
1951
 
1766
- def run_tool_from_source(
1952
+ async def run_tool_from_source(
1767
1953
  self,
1768
1954
  actor: User,
1769
1955
  tool_args: Dict[str, str],
@@ -1796,8 +1982,20 @@ class SyncServer(Server):
1796
1982
 
1797
1983
  # Next, attempt to run the tool with the sandbox
1798
1984
  try:
1799
- tool_execution_result = ToolExecutionSandbox(tool.name, tool_args, actor, tool_object=tool).run(
1800
- agent_state=agent_state, additional_env_vars=tool_env_vars
1985
+ tool_execution_manager = ToolExecutionManager(
1986
+ agent_state=agent_state,
1987
+ message_manager=self.message_manager,
1988
+ agent_manager=self.agent_manager,
1989
+ block_manager=self.block_manager,
1990
+ passage_manager=self.passage_manager,
1991
+ actor=actor,
1992
+ sandbox_env_vars=tool_env_vars,
1993
+ )
1994
+ # TODO: Integrate sandbox result
1995
+ tool_execution_result = await tool_execution_manager.execute_tool_async(
1996
+ function_name=tool_name,
1997
+ function_args=tool_args,
1998
+ tool=tool,
1801
1999
  )
1802
2000
  return ToolReturnMessage(
1803
2001
  id="null",
@@ -1852,7 +2050,8 @@ class SyncServer(Server):
1852
2050
 
1853
2051
  # TODO implement non-flatfile mechanism
1854
2052
  if not tool_settings.mcp_read_from_config:
1855
- raise RuntimeError("MCP config file disabled. Enable it in settings.")
2053
+ return {}
2054
+ # raise RuntimeError("MCP config file disabled. Enable it in settings.")
1856
2055
 
1857
2056
  mcp_server_list = {}
1858
2057
 
@@ -1906,14 +2105,14 @@ class SyncServer(Server):
1906
2105
  # If the file doesn't exist, return empty dictionary
1907
2106
  return mcp_server_list
1908
2107
 
1909
- def get_tools_from_mcp_server(self, mcp_server_name: str) -> List[MCPTool]:
2108
+ async def get_tools_from_mcp_server(self, mcp_server_name: str) -> List[MCPTool]:
1910
2109
  """List the tools in an MCP server. Requires a client to be created."""
1911
2110
  if mcp_server_name not in self.mcp_clients:
1912
2111
  raise ValueError(f"No client was created for MCP server: {mcp_server_name}")
1913
2112
 
1914
- return self.mcp_clients[mcp_server_name].list_tools()
2113
+ return await self.mcp_clients[mcp_server_name].list_tools()
1915
2114
 
1916
- def add_mcp_server_to_config(
2115
+ async def add_mcp_server_to_config(
1917
2116
  self, server_config: Union[SSEServerConfig, StdioServerConfig], allow_upsert: bool = True
1918
2117
  ) -> List[Union[SSEServerConfig, StdioServerConfig]]:
1919
2118
  """Add a new server config to the MCP config file"""
@@ -1942,19 +2141,19 @@ class SyncServer(Server):
1942
2141
 
1943
2142
  # Attempt to initialize the connection to the server
1944
2143
  if server_config.type == MCPServerType.SSE:
1945
- new_mcp_client = SSEMCPClient(server_config)
2144
+ new_mcp_client = AsyncSSEMCPClient(server_config)
1946
2145
  elif server_config.type == MCPServerType.STDIO:
1947
- new_mcp_client = StdioMCPClient(server_config)
2146
+ new_mcp_client = AsyncStdioMCPClient(server_config)
1948
2147
  else:
1949
2148
  raise ValueError(f"Invalid MCP server config: {server_config}")
1950
2149
  try:
1951
- new_mcp_client.connect_to_server()
2150
+ await new_mcp_client.connect_to_server()
1952
2151
  except:
1953
2152
  logger.exception(f"Failed to connect to MCP server: {server_config.server_name}")
1954
2153
  raise RuntimeError(f"Failed to connect to MCP server: {server_config.server_name}")
1955
2154
  # Print out the tools that are connected
1956
2155
  logger.info(f"Attempting to fetch tools from MCP server: {server_config.server_name}")
1957
- new_mcp_tools = new_mcp_client.list_tools()
2156
+ new_mcp_tools = await new_mcp_client.list_tools()
1958
2157
  logger.info(f"MCP tools connected: {', '.join([t.name for t in new_mcp_tools])}")
1959
2158
  logger.debug(f"MCP tools: {', '.join([str(t) for t in new_mcp_tools])}")
1960
2159