letta-nightly 0.6.2.dev20241210104242__py3-none-any.whl → 0.6.3.dev20241211050151__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.

Potentially problematic release.


This version of letta-nightly might be problematic. Click here for more details.

Files changed (44) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +32 -43
  3. letta/agent_store/db.py +12 -54
  4. letta/agent_store/storage.py +10 -9
  5. letta/cli/cli.py +1 -0
  6. letta/client/client.py +3 -2
  7. letta/config.py +2 -2
  8. letta/data_sources/connectors.py +4 -3
  9. letta/embeddings.py +29 -9
  10. letta/functions/function_sets/base.py +36 -11
  11. letta/metadata.py +13 -2
  12. letta/o1_agent.py +2 -3
  13. letta/offline_memory_agent.py +2 -1
  14. letta/orm/__init__.py +1 -0
  15. letta/orm/file.py +1 -0
  16. letta/orm/mixins.py +12 -2
  17. letta/orm/organization.py +3 -0
  18. letta/orm/passage.py +72 -0
  19. letta/orm/sqlalchemy_base.py +36 -7
  20. letta/orm/sqlite_functions.py +140 -0
  21. letta/orm/user.py +1 -1
  22. letta/schemas/agent.py +4 -3
  23. letta/schemas/letta_message.py +5 -1
  24. letta/schemas/letta_request.py +3 -3
  25. letta/schemas/passage.py +6 -4
  26. letta/schemas/sandbox_config.py +1 -0
  27. letta/schemas/tool_rule.py +0 -3
  28. letta/server/rest_api/app.py +34 -12
  29. letta/server/rest_api/routers/v1/agents.py +19 -6
  30. letta/server/server.py +182 -43
  31. letta/server/static_files/assets/{index-4848e3d7.js → index-048c9598.js} +1 -1
  32. letta/server/static_files/assets/{index-43ab4d62.css → index-0e31b727.css} +1 -1
  33. letta/server/static_files/index.html +2 -2
  34. letta/services/passage_manager.py +225 -0
  35. letta/services/source_manager.py +2 -1
  36. letta/services/tool_execution_sandbox.py +18 -6
  37. letta/settings.py +2 -0
  38. letta_nightly-0.6.3.dev20241211050151.dist-info/METADATA +375 -0
  39. {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.3.dev20241211050151.dist-info}/RECORD +42 -40
  40. letta/agent_store/chroma.py +0 -297
  41. letta_nightly-0.6.2.dev20241210104242.dist-info/METADATA +0 -212
  42. {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.3.dev20241211050151.dist-info}/LICENSE +0 -0
  43. {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.3.dev20241211050151.dist-info}/WHEEL +0 -0
  44. {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.3.dev20241211050151.dist-info}/entry_points.txt +0 -0
letta/server/server.py CHANGED
@@ -16,7 +16,6 @@ import letta.constants as constants
16
16
  import letta.server.utils as server_utils
17
17
  import letta.system as system
18
18
  from letta.agent import Agent, save_agent
19
- from letta.agent_store.db import attach_base
20
19
  from letta.agent_store.storage import StorageConnector, TableType
21
20
  from letta.chat_only_agent import ChatOnlyAgent
22
21
  from letta.credentials import LettaCredentials
@@ -70,17 +69,18 @@ from letta.schemas.memory import (
70
69
  )
71
70
  from letta.schemas.message import Message, MessageCreate, MessageRole, MessageUpdate
72
71
  from letta.schemas.organization import Organization
73
- from letta.schemas.passage import Passage
72
+ from letta.schemas.passage import Passage as PydanticPassage
74
73
  from letta.schemas.source import Source
75
74
  from letta.schemas.tool import Tool, ToolCreate
76
75
  from letta.schemas.usage import LettaUsageStatistics
77
- from letta.schemas.user import User
76
+ from letta.schemas.user import User as PydanticUser
78
77
  from letta.services.agents_tags_manager import AgentsTagsManager
79
78
  from letta.services.block_manager import BlockManager
80
79
  from letta.services.blocks_agents_manager import BlocksAgentsManager
81
80
  from letta.services.job_manager import JobManager
82
81
  from letta.services.message_manager import MessageManager
83
82
  from letta.services.organization_manager import OrganizationManager
83
+ from letta.services.passage_manager import PassageManager
84
84
  from letta.services.per_agent_lock_manager import PerAgentLockManager
85
85
  from letta.services.sandbox_config_manager import SandboxConfigManager
86
86
  from letta.services.source_manager import SourceManager
@@ -125,7 +125,7 @@ class Server(object):
125
125
  def create_agent(
126
126
  self,
127
127
  request: CreateAgent,
128
- actor: User,
128
+ actor: PydanticUser,
129
129
  # interface
130
130
  interface: Union[AgentInterface, None] = None,
131
131
  ) -> AgentState:
@@ -156,6 +156,11 @@ class Server(object):
156
156
  raise NotImplementedError
157
157
 
158
158
 
159
+ from contextlib import contextmanager
160
+
161
+ from rich.console import Console
162
+ from rich.panel import Panel
163
+ from rich.text import Text
159
164
  from sqlalchemy import create_engine
160
165
  from sqlalchemy.orm import sessionmaker
161
166
 
@@ -166,7 +171,36 @@ from letta.settings import model_settings, settings, tool_settings
166
171
 
167
172
  config = LettaConfig.load()
168
173
 
169
- attach_base()
174
+
175
+ def print_sqlite_schema_error():
176
+ """Print a formatted error message for SQLite schema issues"""
177
+ console = Console()
178
+ error_text = Text()
179
+ error_text.append("Existing SQLite DB schema is invalid, and schema migrations are not supported for SQLite. ", style="bold red")
180
+ error_text.append("To have migrations supported between Letta versions, please run Letta with Docker (", style="white")
181
+ error_text.append("https://docs.letta.com/server/docker", style="blue underline")
182
+ error_text.append(") or use Postgres by setting ", style="white")
183
+ error_text.append("LETTA_PG_URI", style="yellow")
184
+ error_text.append(".\n\n", style="white")
185
+ error_text.append("If you wish to keep using SQLite, you can reset your database by removing the DB file with ", style="white")
186
+ error_text.append("rm ~/.letta/sqlite.db", style="yellow")
187
+ error_text.append(" or downgrade to your previous version of Letta.", style="white")
188
+
189
+ console.print(Panel(error_text, border_style="red"))
190
+
191
+
192
+ @contextmanager
193
+ def db_error_handler():
194
+ """Context manager for handling database errors"""
195
+ try:
196
+ yield
197
+ except Exception as e:
198
+ # Handle other SQLAlchemy errors
199
+ print(e)
200
+ print_sqlite_schema_error()
201
+ # raise ValueError(f"SQLite DB error: {str(e)}")
202
+ exit(1)
203
+
170
204
 
171
205
  if settings.letta_pg_uri_no_default:
172
206
  config.recall_storage_type = "postgres"
@@ -180,6 +214,30 @@ else:
180
214
  # TODO: don't rely on config storage
181
215
  engine = create_engine("sqlite:///" + os.path.join(config.recall_storage_path, "sqlite.db"))
182
216
 
217
+ # Store the original connect method
218
+ original_connect = engine.connect
219
+
220
+ def wrapped_connect(*args, **kwargs):
221
+ with db_error_handler():
222
+ # Get the connection
223
+ connection = original_connect(*args, **kwargs)
224
+
225
+ # Store the original execution method
226
+ original_execute = connection.execute
227
+
228
+ # Wrap the execute method of the connection
229
+ def wrapped_execute(*args, **kwargs):
230
+ with db_error_handler():
231
+ return original_execute(*args, **kwargs)
232
+
233
+ # Replace the connection's execute method
234
+ connection.execute = wrapped_execute
235
+
236
+ return connection
237
+
238
+ # Replace the engine's connect method
239
+ engine.connect = wrapped_connect
240
+
183
241
  Base.metadata.create_all(bind=engine)
184
242
 
185
243
  SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@@ -245,6 +303,7 @@ class SyncServer(Server):
245
303
 
246
304
  # Managers that interface with data models
247
305
  self.organization_manager = OrganizationManager()
306
+ self.passage_manager = PassageManager()
248
307
  self.user_manager = UserManager()
249
308
  self.tool_manager = ToolManager()
250
309
  self.block_manager = BlockManager()
@@ -379,6 +438,10 @@ class SyncServer(Server):
379
438
  interface = interface or self.default_interface_factory()
380
439
  if agent_state.agent_type == AgentType.memgpt_agent:
381
440
  agent = Agent(agent_state=agent_state, interface=interface, user=actor, initial_message_sequence=initial_message_sequence)
441
+ elif agent_state.agent_type == AgentType.offline_memory_agent:
442
+ agent = OfflineMemoryAgent(
443
+ agent_state=agent_state, interface=interface, user=actor, initial_message_sequence=initial_message_sequence
444
+ )
382
445
  else:
383
446
  assert initial_message_sequence is None, f"Initial message sequence is not supported for O1Agents"
384
447
  agent = O1Agent(agent_state=agent_state, interface=interface, user=actor)
@@ -496,7 +559,12 @@ class SyncServer(Server):
496
559
 
497
560
  # attach data to agent from source
498
561
  source_connector = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
499
- letta_agent.attach_source(data_source, source_connector, self.ms)
562
+ letta_agent.attach_source(
563
+ user=self.user_manager.get_user_by_id(user_id=user_id),
564
+ source_id=data_source,
565
+ source_manager=letta_agent.source_manager,
566
+ ms=self.ms,
567
+ )
500
568
 
501
569
  elif command.lower() == "dump" or command.lower().startswith("dump "):
502
570
  # Check if there's an additional argument that's an integer
@@ -511,7 +579,7 @@ class SyncServer(Server):
511
579
  letta_agent.interface.print_messages_raw(letta_agent.messages)
512
580
 
513
581
  elif command.lower() == "memory":
514
- ret_str = f"\nDumping memory contents:\n" + f"\n{str(letta_agent.agent_state.memory)}" + f"\n{str(letta_agent.archival_memory)}"
582
+ ret_str = f"\nDumping memory contents:\n" + f"\n{str(letta_agent.agent_state.memory)}" + f"\n{str(letta_agent.passage_manager)}"
515
583
  return ret_str
516
584
 
517
585
  elif command.lower() == "pop" or command.lower().startswith("pop "):
@@ -767,7 +835,7 @@ class SyncServer(Server):
767
835
  def create_agent(
768
836
  self,
769
837
  request: CreateAgent,
770
- actor: User,
838
+ actor: PydanticUser,
771
839
  # interface
772
840
  interface: Union[AgentInterface, None] = None,
773
841
  ) -> AgentState:
@@ -824,6 +892,12 @@ class SyncServer(Server):
824
892
  if not user:
825
893
  raise ValueError(f"cannot find user with associated client id: {user_id}")
826
894
 
895
+ if request.llm_config is None:
896
+ raise ValueError("llm_config is required")
897
+
898
+ if request.embedding_config is None:
899
+ raise ValueError("embedding_config is required")
900
+
827
901
  # created and persist the agent state in the DB
828
902
  agent_state = PersistedAgentState(
829
903
  name=request.name,
@@ -843,7 +917,7 @@ class SyncServer(Server):
843
917
  self.ms.create_agent(agent_state)
844
918
 
845
919
  # create the agent object
846
- if request.initial_message_sequence:
920
+ if request.initial_message_sequence is not None:
847
921
  # init_messages = [Message(user_id=user_id, agent_id=agent_state.id, role=message.role, text=message.text) for message in request.initial_message_sequence]
848
922
  init_messages = []
849
923
  for message in request.initial_message_sequence:
@@ -913,6 +987,7 @@ class SyncServer(Server):
913
987
 
914
988
  # get `Tool` objects
915
989
  tools = [self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=user) for tool_name in agent_state.tool_names]
990
+ tools = [tool for tool in tools if tool is not None]
916
991
 
917
992
  # get `Source` objects
918
993
  sources = self.list_attached_sources(agent_id=agent_id)
@@ -926,7 +1001,7 @@ class SyncServer(Server):
926
1001
  def update_agent(
927
1002
  self,
928
1003
  request: UpdateAgentState,
929
- actor: User,
1004
+ actor: PydanticUser,
930
1005
  ) -> AgentState:
931
1006
  """Update the agents core memory block, return the new state"""
932
1007
  try:
@@ -1143,7 +1218,7 @@ class SyncServer(Server):
1143
1218
 
1144
1219
  def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
1145
1220
  agent = self.load_agent(agent_id=agent_id)
1146
- return ArchivalMemorySummary(size=len(agent.archival_memory))
1221
+ return ArchivalMemorySummary(size=agent.passage_manager.size(actor=self.default_user))
1147
1222
 
1148
1223
  def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
1149
1224
  agent = self.load_agent(agent_id=agent_id)
@@ -1168,7 +1243,56 @@ class SyncServer(Server):
1168
1243
  message = agent.message_manager.get_message_by_id(id=message_id, actor=self.default_user)
1169
1244
  return message
1170
1245
 
1171
- def get_agent_archival(self, user_id: str, agent_id: str, start: int, count: int) -> List[Passage]:
1246
+ def get_agent_messages(
1247
+ self,
1248
+ agent_id: str,
1249
+ start: int,
1250
+ count: int,
1251
+ ) -> Union[List[Message], List[LettaMessage]]:
1252
+ """Paginated query of all messages in agent message queue"""
1253
+ # Get the agent object (loaded in memory)
1254
+ letta_agent = self.load_agent(agent_id=agent_id)
1255
+
1256
+ if start < 0 or count < 0:
1257
+ raise ValueError("Start and count values should be non-negative")
1258
+
1259
+ if start + count < len(letta_agent._messages): # messages can be returned from whats in memory
1260
+ # Reverse the list to make it in reverse chronological order
1261
+ reversed_messages = letta_agent._messages[::-1]
1262
+ # Check if start is within the range of the list
1263
+ if start >= len(reversed_messages):
1264
+ raise IndexError("Start index is out of range")
1265
+
1266
+ # Calculate the end index, ensuring it does not exceed the list length
1267
+ end_index = min(start + count, len(reversed_messages))
1268
+
1269
+ # Slice the list for pagination
1270
+ messages = reversed_messages[start:end_index]
1271
+
1272
+ else:
1273
+ # need to access persistence manager for additional messages
1274
+
1275
+ # get messages using message manager
1276
+ page = letta_agent.message_manager.list_user_messages_for_agent(
1277
+ agent_id=agent_id,
1278
+ actor=self.default_user,
1279
+ cursor=start,
1280
+ limit=count,
1281
+ )
1282
+
1283
+ messages = page
1284
+ assert all(isinstance(m, Message) for m in messages)
1285
+
1286
+ ## Convert to json
1287
+ ## Add a tag indicating in-context or not
1288
+ # json_messages = [record.to_json() for record in messages]
1289
+ # in_context_message_ids = [str(m.id) for m in letta_agent._messages]
1290
+ # for d in json_messages:
1291
+ # d["in_context"] = True if str(d["id"]) in in_context_message_ids else False
1292
+
1293
+ return messages
1294
+
1295
+ def get_agent_archival(self, user_id: str, agent_id: str, cursor: Optional[str] = None, limit: int = 50) -> List[PydanticPassage]:
1172
1296
  """Paginated query of all messages in agent archival memory"""
1173
1297
  if self.user_manager.get_user_by_id(user_id=user_id) is None:
1174
1298
  raise ValueError(f"User user_id={user_id} does not exist")
@@ -1179,22 +1303,22 @@ class SyncServer(Server):
1179
1303
  letta_agent = self.load_agent(agent_id=agent_id)
1180
1304
 
1181
1305
  # iterate over records
1182
- db_iterator = letta_agent.archival_memory.storage.get_all_paginated(page_size=count, offset=start)
1306
+ records = letta_agent.passage_manager.list_passages(
1307
+ actor=self.default_user,
1308
+ agent_id=agent_id,
1309
+ cursor=cursor,
1310
+ limit=limit,
1311
+ )
1183
1312
 
1184
- # get a single page of messages
1185
- page = next(db_iterator, [])
1186
- return page
1313
+ return records
1187
1314
 
1188
1315
  def get_agent_archival_cursor(
1189
1316
  self,
1190
1317
  user_id: str,
1191
1318
  agent_id: str,
1192
- after: Optional[str] = None,
1193
- before: Optional[str] = None,
1319
+ cursor: Optional[str] = None,
1194
1320
  limit: Optional[int] = 100,
1195
- order_by: Optional[str] = "created_at",
1196
- reverse: Optional[bool] = False,
1197
- ) -> List[Passage]:
1321
+ ) -> List[PydanticPassage]:
1198
1322
  if self.user_manager.get_user_by_id(user_id=user_id) is None:
1199
1323
  raise LettaUserNotFoundError(f"User user_id={user_id} does not exist")
1200
1324
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
@@ -1203,14 +1327,18 @@ class SyncServer(Server):
1203
1327
  # Get the agent object (loaded in memory)
1204
1328
  letta_agent = self.load_agent(agent_id=agent_id)
1205
1329
 
1206
- # iterate over recorde
1207
- cursor, records = letta_agent.archival_memory.storage.get_all_cursor(
1208
- after=after, before=before, limit=limit, order_by=order_by, reverse=reverse
1330
+ # iterate over records
1331
+ records = letta_agent.passage_manager.list_passages(
1332
+ actor=self.default_user,
1333
+ agent_id=agent_id,
1334
+ cursor=cursor,
1335
+ limit=limit,
1209
1336
  )
1210
1337
  return records
1211
1338
 
1212
- def insert_archival_memory(self, user_id: str, agent_id: str, memory_contents: str) -> List[Passage]:
1213
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1339
+ def insert_archival_memory(self, user_id: str, agent_id: str, memory_contents: str) -> List[PydanticPassage]:
1340
+ actor = self.user_manager.get_user_by_id(user_id=user_id)
1341
+ if actor is None:
1214
1342
  raise ValueError(f"User user_id={user_id} does not exist")
1215
1343
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1216
1344
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1219,17 +1347,20 @@ class SyncServer(Server):
1219
1347
  letta_agent = self.load_agent(agent_id=agent_id)
1220
1348
 
1221
1349
  # Insert into archival memory
1222
- passage_ids = letta_agent.archival_memory.insert(memory_string=memory_contents, return_ids=True)
1350
+ passage_ids = self.passage_manager.insert_passage(
1351
+ agent_state=letta_agent.agent_state, agent_id=agent_id, text=memory_contents, actor=actor, return_ids=True
1352
+ )
1223
1353
 
1224
1354
  # Update the agent
1225
1355
  # TODO: should this update the system prompt?
1226
1356
  save_agent(letta_agent, self.ms)
1227
1357
 
1228
1358
  # TODO: this is gross, fix
1229
- return [letta_agent.archival_memory.storage.get(id=passage_id) for passage_id in passage_ids]
1359
+ return [self.passage_manager.get_passage_by_id(passage_id=passage_id, actor=actor) for passage_id in passage_ids]
1230
1360
 
1231
1361
  def delete_archival_memory(self, user_id: str, agent_id: str, memory_id: str):
1232
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1362
+ actor = self.user_manager.get_user_by_id(user_id=user_id)
1363
+ if actor is None:
1233
1364
  raise ValueError(f"User user_id={user_id} does not exist")
1234
1365
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1235
1366
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1241,7 +1372,7 @@ class SyncServer(Server):
1241
1372
 
1242
1373
  # Delete by ID
1243
1374
  # TODO check if it exists first, and throw error if not
1244
- letta_agent.archival_memory.storage.delete({"id": memory_id})
1375
+ letta_agent.passage_manager.delete_passage_by_id(passage_id=memory_id, actor=actor)
1245
1376
 
1246
1377
  # TODO: return archival memory
1247
1378
 
@@ -1387,6 +1518,12 @@ class SyncServer(Server):
1387
1518
  except NoResultFound:
1388
1519
  logger.error(f"Agent with id {agent_state.id} has nonexistent user {agent_state.user_id}")
1389
1520
 
1521
+ # delete all passages associated with this agent
1522
+ # TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM
1523
+ passages = self.passage_manager.list_passages(actor=actor, agent_id=agent_state.id)
1524
+ for passage in passages:
1525
+ self.passage_manager.delete_passage_by_id(passage.id, actor=actor)
1526
+
1390
1527
  # First, if the agent is in the in-memory cache we should remove it
1391
1528
  # List of {'user_id': user_id, 'agent_id': agent_id, 'agent': agent_obj} dicts
1392
1529
  try:
@@ -1429,7 +1566,7 @@ class SyncServer(Server):
1429
1566
  self.ms.delete_api_key(api_key=api_key)
1430
1567
  return api_key_obj
1431
1568
 
1432
- def delete_source(self, source_id: str, actor: User):
1569
+ def delete_source(self, source_id: str, actor: PydanticUser):
1433
1570
  """Delete a data source"""
1434
1571
  self.source_manager.delete_source(source_id=source_id, actor=actor)
1435
1572
 
@@ -1439,7 +1576,7 @@ class SyncServer(Server):
1439
1576
 
1440
1577
  # TODO: delete data from agent passage stores (?)
1441
1578
 
1442
- def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: User) -> Job:
1579
+ def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: PydanticUser) -> Job:
1443
1580
 
1444
1581
  # update job
1445
1582
  job = self.job_manager.get_job_by_id(job_id, actor=actor)
@@ -1466,6 +1603,7 @@ class SyncServer(Server):
1466
1603
  user_id: str,
1467
1604
  connector: DataConnector,
1468
1605
  source_name: str,
1606
+ agent_id: Optional[str] = None,
1469
1607
  ) -> Tuple[int, int]:
1470
1608
  """Load data from a DataConnector into a source for a specified user_id"""
1471
1609
  # TODO: this should be implemented as a batch job or at least async, since it may take a long time
@@ -1480,14 +1618,13 @@ class SyncServer(Server):
1480
1618
  passage_store = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
1481
1619
 
1482
1620
  # load data into the document store
1483
- passage_count, document_count = load_data(connector, source, passage_store, self.source_manager, actor=user)
1621
+ passage_count, document_count = load_data(connector, source, passage_store, self.source_manager, actor=user, agent_id=agent_id)
1484
1622
  return passage_count, document_count
1485
1623
 
1486
1624
  def attach_source_to_agent(
1487
1625
  self,
1488
1626
  user_id: str,
1489
1627
  agent_id: str,
1490
- # source_id: str,
1491
1628
  source_id: Optional[str] = None,
1492
1629
  source_name: Optional[str] = None,
1493
1630
  ) -> Source:
@@ -1499,15 +1636,14 @@ class SyncServer(Server):
1499
1636
  data_source = self.source_manager.get_source_by_name(source_name=source_name, actor=user)
1500
1637
  else:
1501
1638
  raise ValueError(f"Need to provide at least source_id or source_name to find the source.")
1502
- # get connection to data source storage
1503
- source_connector = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
1639
+
1504
1640
  assert data_source, f"Data source with id={source_id} or name={source_name} does not exist"
1505
1641
 
1506
1642
  # load agent
1507
1643
  agent = self.load_agent(agent_id=agent_id)
1508
1644
 
1509
1645
  # attach source to agent
1510
- agent.attach_source(data_source.id, source_connector, self.ms)
1646
+ agent.attach_source(user=user, source_id=data_source.id, source_manager=self.source_manager, ms=self.ms)
1511
1647
 
1512
1648
  return data_source
1513
1649
 
@@ -1530,8 +1666,7 @@ class SyncServer(Server):
1530
1666
 
1531
1667
  # delete all Passage objects with source_id==source_id from agent's archival memory
1532
1668
  agent = self.load_agent(agent_id=agent_id)
1533
- archival_memory = agent.archival_memory
1534
- archival_memory.storage.delete({"source_id": source_id})
1669
+ agent.passage_manager.delete_passages(actor=user, limit=100, source_id=source_id)
1535
1670
 
1536
1671
  # delete agent-source mapping
1537
1672
  self.ms.detach_source(agent_id=agent_id, source_id=source_id)
@@ -1545,11 +1680,11 @@ class SyncServer(Server):
1545
1680
 
1546
1681
  return [self.source_manager.get_source_by_id(source_id=id) for id in source_ids]
1547
1682
 
1548
- def list_data_source_passages(self, user_id: str, source_id: str) -> List[Passage]:
1683
+ def list_data_source_passages(self, user_id: str, source_id: str) -> List[PydanticPassage]:
1549
1684
  warnings.warn("list_data_source_passages is not yet implemented, returning empty list.", category=UserWarning)
1550
1685
  return []
1551
1686
 
1552
- def list_all_sources(self, actor: User) -> List[Source]:
1687
+ def list_all_sources(self, actor: PydanticUser) -> List[Source]:
1553
1688
  """List all sources (w/ extra metadata) belonging to a user"""
1554
1689
 
1555
1690
  sources = self.source_manager.list_sources(actor=actor)
@@ -1589,7 +1724,7 @@ class SyncServer(Server):
1589
1724
 
1590
1725
  return sources_with_metadata
1591
1726
 
1592
- def add_default_external_tools(self, actor: User) -> bool:
1727
+ def add_default_external_tools(self, actor: PydanticUser) -> bool:
1593
1728
  """Add default langchain tools. Return true if successful, false otherwise."""
1594
1729
  success = True
1595
1730
  tool_creates = ToolCreate.load_default_langchain_tools()
@@ -1646,7 +1781,7 @@ class SyncServer(Server):
1646
1781
  save_agent(letta_agent, self.ms)
1647
1782
  return response
1648
1783
 
1649
- def get_user_or_default(self, user_id: Optional[str]) -> User:
1784
+ def get_user_or_default(self, user_id: Optional[str]) -> PydanticUser:
1650
1785
  """Get the user object for user_id if it exists, otherwise return the default user object"""
1651
1786
  if user_id is None:
1652
1787
  user_id = self.user_manager.DEFAULT_USER_ID
@@ -1827,6 +1962,8 @@ class SyncServer(Server):
1827
1962
  date=get_utc_time(),
1828
1963
  status="success",
1829
1964
  function_return=function_response,
1965
+ stdout=sandbox_run_result.stdout,
1966
+ stderr=sandbox_run_result.stderr,
1830
1967
  )
1831
1968
  except Exception as e:
1832
1969
  # same as agent.py
@@ -1842,6 +1979,8 @@ class SyncServer(Server):
1842
1979
  date=get_utc_time(),
1843
1980
  status="error",
1844
1981
  function_return=error_msg,
1982
+ stdout=[""],
1983
+ stderr=[traceback.format_exc()],
1845
1984
  )
1846
1985
 
1847
1986
  # Composio wrappers