letta-nightly 0.6.2.dev20241210030340__py3-none-any.whl → 0.6.2.dev20241211031658__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 (43) hide show
  1. letta/agent.py +32 -43
  2. letta/agent_store/db.py +12 -54
  3. letta/agent_store/storage.py +10 -9
  4. letta/cli/cli.py +1 -0
  5. letta/client/client.py +4 -3
  6. letta/config.py +2 -2
  7. letta/data_sources/connectors.py +4 -3
  8. letta/embeddings.py +29 -9
  9. letta/functions/function_sets/base.py +36 -11
  10. letta/metadata.py +13 -2
  11. letta/o1_agent.py +2 -3
  12. letta/offline_memory_agent.py +2 -1
  13. letta/orm/__init__.py +1 -0
  14. letta/orm/file.py +1 -0
  15. letta/orm/mixins.py +12 -2
  16. letta/orm/organization.py +3 -0
  17. letta/orm/passage.py +72 -0
  18. letta/orm/sqlalchemy_base.py +66 -10
  19. letta/orm/sqlite_functions.py +140 -0
  20. letta/orm/user.py +1 -1
  21. letta/schemas/agent.py +4 -3
  22. letta/schemas/letta_message.py +5 -1
  23. letta/schemas/letta_request.py +3 -3
  24. letta/schemas/passage.py +6 -4
  25. letta/schemas/sandbox_config.py +1 -0
  26. letta/schemas/tool_rule.py +0 -3
  27. letta/server/rest_api/app.py +34 -12
  28. letta/server/rest_api/routers/v1/agents.py +20 -7
  29. letta/server/server.py +76 -52
  30. letta/server/static_files/assets/{index-4848e3d7.js → index-048c9598.js} +1 -1
  31. letta/server/static_files/assets/{index-43ab4d62.css → index-0e31b727.css} +1 -1
  32. letta/server/static_files/index.html +2 -2
  33. letta/services/message_manager.py +3 -0
  34. letta/services/passage_manager.py +225 -0
  35. letta/services/source_manager.py +2 -1
  36. letta/services/tool_execution_sandbox.py +19 -7
  37. letta/settings.py +2 -0
  38. {letta_nightly-0.6.2.dev20241210030340.dist-info → letta_nightly-0.6.2.dev20241211031658.dist-info}/METADATA +10 -15
  39. {letta_nightly-0.6.2.dev20241210030340.dist-info → letta_nightly-0.6.2.dev20241211031658.dist-info}/RECORD +42 -40
  40. letta/agent_store/chroma.py +0 -297
  41. {letta_nightly-0.6.2.dev20241210030340.dist-info → letta_nightly-0.6.2.dev20241211031658.dist-info}/LICENSE +0 -0
  42. {letta_nightly-0.6.2.dev20241210030340.dist-info → letta_nightly-0.6.2.dev20241211031658.dist-info}/WHEEL +0 -0
  43. {letta_nightly-0.6.2.dev20241210030340.dist-info → letta_nightly-0.6.2.dev20241211031658.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
@@ -101,11 +101,6 @@ class Server(object):
101
101
  """List all available agents to a user"""
102
102
  raise NotImplementedError
103
103
 
104
- @abstractmethod
105
- def get_agent_messages(self, user_id: str, agent_id: str, start: int, count: int) -> list:
106
- """Paginated query of in-context messages in agent message queue"""
107
- raise NotImplementedError
108
-
109
104
  @abstractmethod
110
105
  def get_agent_memory(self, user_id: str, agent_id: str) -> dict:
111
106
  """Return the memory of an agent (core memory + non-core statistics)"""
@@ -130,7 +125,7 @@ class Server(object):
130
125
  def create_agent(
131
126
  self,
132
127
  request: CreateAgent,
133
- actor: User,
128
+ actor: PydanticUser,
134
129
  # interface
135
130
  interface: Union[AgentInterface, None] = None,
136
131
  ) -> AgentState:
@@ -171,8 +166,6 @@ from letta.settings import model_settings, settings, tool_settings
171
166
 
172
167
  config = LettaConfig.load()
173
168
 
174
- attach_base()
175
-
176
169
  if settings.letta_pg_uri_no_default:
177
170
  config.recall_storage_type = "postgres"
178
171
  config.recall_storage_uri = settings.letta_pg_uri_no_default
@@ -250,6 +243,7 @@ class SyncServer(Server):
250
243
 
251
244
  # Managers that interface with data models
252
245
  self.organization_manager = OrganizationManager()
246
+ self.passage_manager = PassageManager()
253
247
  self.user_manager = UserManager()
254
248
  self.tool_manager = ToolManager()
255
249
  self.block_manager = BlockManager()
@@ -384,6 +378,8 @@ class SyncServer(Server):
384
378
  interface = interface or self.default_interface_factory()
385
379
  if agent_state.agent_type == AgentType.memgpt_agent:
386
380
  agent = Agent(agent_state=agent_state, interface=interface, user=actor, initial_message_sequence=initial_message_sequence)
381
+ elif agent_state.agent_type == AgentType.offline_memory_agent:
382
+ agent = OfflineMemoryAgent(agent_state=agent_state, interface=interface, user=actor, initial_message_sequence=initial_message_sequence)
387
383
  else:
388
384
  assert initial_message_sequence is None, f"Initial message sequence is not supported for O1Agents"
389
385
  agent = O1Agent(agent_state=agent_state, interface=interface, user=actor)
@@ -501,7 +497,12 @@ class SyncServer(Server):
501
497
 
502
498
  # attach data to agent from source
503
499
  source_connector = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
504
- letta_agent.attach_source(data_source, source_connector, self.ms)
500
+ letta_agent.attach_source(
501
+ user=self.user_manager.get_user_by_id(user_id=user_id),
502
+ source_id=data_source,
503
+ source_manager=letta_agent.source_manager,
504
+ ms=self.ms
505
+ )
505
506
 
506
507
  elif command.lower() == "dump" or command.lower().startswith("dump "):
507
508
  # Check if there's an additional argument that's an integer
@@ -516,7 +517,7 @@ class SyncServer(Server):
516
517
  letta_agent.interface.print_messages_raw(letta_agent.messages)
517
518
 
518
519
  elif command.lower() == "memory":
519
- ret_str = f"\nDumping memory contents:\n" + f"\n{str(letta_agent.agent_state.memory)}" + f"\n{str(letta_agent.archival_memory)}"
520
+ ret_str = f"\nDumping memory contents:\n" + f"\n{str(letta_agent.agent_state.memory)}" + f"\n{str(letta_agent.passage_manager)}"
520
521
  return ret_str
521
522
 
522
523
  elif command.lower() == "pop" or command.lower().startswith("pop "):
@@ -772,7 +773,7 @@ class SyncServer(Server):
772
773
  def create_agent(
773
774
  self,
774
775
  request: CreateAgent,
775
- actor: User,
776
+ actor: PydanticUser,
776
777
  # interface
777
778
  interface: Union[AgentInterface, None] = None,
778
779
  ) -> AgentState:
@@ -829,6 +830,12 @@ class SyncServer(Server):
829
830
  if not user:
830
831
  raise ValueError(f"cannot find user with associated client id: {user_id}")
831
832
 
833
+ if request.llm_config is None:
834
+ raise ValueError("llm_config is required")
835
+
836
+ if request.embedding_config is None:
837
+ raise ValueError("embedding_config is required")
838
+
832
839
  # created and persist the agent state in the DB
833
840
  agent_state = PersistedAgentState(
834
841
  name=request.name,
@@ -848,7 +855,7 @@ class SyncServer(Server):
848
855
  self.ms.create_agent(agent_state)
849
856
 
850
857
  # create the agent object
851
- if request.initial_message_sequence:
858
+ if request.initial_message_sequence is not None:
852
859
  # 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]
853
860
  init_messages = []
854
861
  for message in request.initial_message_sequence:
@@ -918,6 +925,7 @@ class SyncServer(Server):
918
925
 
919
926
  # get `Tool` objects
920
927
  tools = [self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=user) for tool_name in agent_state.tool_names]
928
+ tools = [tool for tool in tools if tool is not None]
921
929
 
922
930
  # get `Source` objects
923
931
  sources = self.list_attached_sources(agent_id=agent_id)
@@ -931,7 +939,7 @@ class SyncServer(Server):
931
939
  def update_agent(
932
940
  self,
933
941
  request: UpdateAgentState,
934
- actor: User,
942
+ actor: PydanticUser,
935
943
  ) -> AgentState:
936
944
  """Update the agents core memory block, return the new state"""
937
945
  try:
@@ -1148,7 +1156,7 @@ class SyncServer(Server):
1148
1156
 
1149
1157
  def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
1150
1158
  agent = self.load_agent(agent_id=agent_id)
1151
- return ArchivalMemorySummary(size=len(agent.archival_memory))
1159
+ return ArchivalMemorySummary(size=agent.passage_manager.size(actor=self.default_user))
1152
1160
 
1153
1161
  def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
1154
1162
  agent = self.load_agent(agent_id=agent_id)
@@ -1222,7 +1230,7 @@ class SyncServer(Server):
1222
1230
 
1223
1231
  return messages
1224
1232
 
1225
- def get_agent_archival(self, user_id: str, agent_id: str, start: int, count: int) -> List[Passage]:
1233
+ def get_agent_archival(self, user_id: str, agent_id: str, cursor: Optional[str] = None, limit: int = 50) -> List[PydanticPassage]:
1226
1234
  """Paginated query of all messages in agent archival memory"""
1227
1235
  if self.user_manager.get_user_by_id(user_id=user_id) is None:
1228
1236
  raise ValueError(f"User user_id={user_id} does not exist")
@@ -1233,22 +1241,22 @@ class SyncServer(Server):
1233
1241
  letta_agent = self.load_agent(agent_id=agent_id)
1234
1242
 
1235
1243
  # iterate over records
1236
- db_iterator = letta_agent.archival_memory.storage.get_all_paginated(page_size=count, offset=start)
1244
+ records = letta_agent.passage_manager.list_passages(
1245
+ actor=self.default_user,
1246
+ agent_id=agent_id,
1247
+ cursor=cursor,
1248
+ limit=limit,
1249
+ )
1237
1250
 
1238
- # get a single page of messages
1239
- page = next(db_iterator, [])
1240
- return page
1251
+ return records
1241
1252
 
1242
1253
  def get_agent_archival_cursor(
1243
1254
  self,
1244
1255
  user_id: str,
1245
1256
  agent_id: str,
1246
- after: Optional[str] = None,
1247
- before: Optional[str] = None,
1257
+ cursor: Optional[str] = None,
1248
1258
  limit: Optional[int] = 100,
1249
- order_by: Optional[str] = "created_at",
1250
- reverse: Optional[bool] = False,
1251
- ) -> List[Passage]:
1259
+ ) -> List[PydanticPassage]:
1252
1260
  if self.user_manager.get_user_by_id(user_id=user_id) is None:
1253
1261
  raise LettaUserNotFoundError(f"User user_id={user_id} does not exist")
1254
1262
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
@@ -1257,14 +1265,15 @@ class SyncServer(Server):
1257
1265
  # Get the agent object (loaded in memory)
1258
1266
  letta_agent = self.load_agent(agent_id=agent_id)
1259
1267
 
1260
- # iterate over recorde
1261
- cursor, records = letta_agent.archival_memory.storage.get_all_cursor(
1262
- after=after, before=before, limit=limit, order_by=order_by, reverse=reverse
1268
+ # iterate over records
1269
+ records = letta_agent.passage_manager.list_passages(
1270
+ actor=self.default_user, agent_id=agent_id, cursor=cursor, limit=limit,
1263
1271
  )
1264
1272
  return records
1265
1273
 
1266
- def insert_archival_memory(self, user_id: str, agent_id: str, memory_contents: str) -> List[Passage]:
1267
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1274
+ def insert_archival_memory(self, user_id: str, agent_id: str, memory_contents: str) -> List[PydanticPassage]:
1275
+ actor = self.user_manager.get_user_by_id(user_id=user_id)
1276
+ if actor is None:
1268
1277
  raise ValueError(f"User user_id={user_id} does not exist")
1269
1278
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1270
1279
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1273,17 +1282,20 @@ class SyncServer(Server):
1273
1282
  letta_agent = self.load_agent(agent_id=agent_id)
1274
1283
 
1275
1284
  # Insert into archival memory
1276
- passage_ids = letta_agent.archival_memory.insert(memory_string=memory_contents, return_ids=True)
1285
+ passage_ids = self.passage_manager.insert_passage(
1286
+ agent_state=letta_agent.agent_state, agent_id=agent_id, text=memory_contents, actor=actor, return_ids=True
1287
+ )
1277
1288
 
1278
1289
  # Update the agent
1279
1290
  # TODO: should this update the system prompt?
1280
1291
  save_agent(letta_agent, self.ms)
1281
1292
 
1282
1293
  # TODO: this is gross, fix
1283
- return [letta_agent.archival_memory.storage.get(id=passage_id) for passage_id in passage_ids]
1294
+ return [self.passage_manager.get_passage_by_id(passage_id=passage_id, actor=actor) for passage_id in passage_ids]
1284
1295
 
1285
1296
  def delete_archival_memory(self, user_id: str, agent_id: str, memory_id: str):
1286
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1297
+ actor = self.user_manager.get_user_by_id(user_id=user_id)
1298
+ if actor is None:
1287
1299
  raise ValueError(f"User user_id={user_id} does not exist")
1288
1300
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1289
1301
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1295,7 +1307,7 @@ class SyncServer(Server):
1295
1307
 
1296
1308
  # Delete by ID
1297
1309
  # TODO check if it exists first, and throw error if not
1298
- letta_agent.archival_memory.storage.delete({"id": memory_id})
1310
+ letta_agent.passage_manager.delete_passage_by_id(passage_id=memory_id, actor=actor)
1299
1311
 
1300
1312
  # TODO: return archival memory
1301
1313
 
@@ -1303,7 +1315,8 @@ class SyncServer(Server):
1303
1315
  self,
1304
1316
  user_id: str,
1305
1317
  agent_id: str,
1306
- cursor: Optional[str] = None,
1318
+ after: Optional[str] = None,
1319
+ before: Optional[str] = None,
1307
1320
  limit: Optional[int] = 100,
1308
1321
  reverse: Optional[bool] = False,
1309
1322
  return_message_object: bool = True,
@@ -1320,12 +1333,15 @@ class SyncServer(Server):
1320
1333
  letta_agent = self.load_agent(agent_id=agent_id)
1321
1334
 
1322
1335
  # iterate over records
1323
- # TODO: Check "order_by", "order"
1336
+ start_date = self.message_manager.get_message_by_id(after, actor=actor).created_at if after else None
1337
+ end_date = self.message_manager.get_message_by_id(before, actor=actor).created_at if before else None
1324
1338
  records = letta_agent.message_manager.list_messages_for_agent(
1325
1339
  agent_id=agent_id,
1326
1340
  actor=actor,
1327
- cursor=cursor,
1341
+ start_date=start_date,
1342
+ end_date=end_date,
1328
1343
  limit=limit,
1344
+ ascending=not reverse,
1329
1345
  )
1330
1346
 
1331
1347
  assert all(isinstance(m, Message) for m in records)
@@ -1437,6 +1453,12 @@ class SyncServer(Server):
1437
1453
  except NoResultFound:
1438
1454
  logger.error(f"Agent with id {agent_state.id} has nonexistent user {agent_state.user_id}")
1439
1455
 
1456
+ # delete all passages associated with this agent
1457
+ # TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM
1458
+ passages = self.passage_manager.list_passages(actor=actor, agent_id=agent_state.id)
1459
+ for passage in passages:
1460
+ self.passage_manager.delete_passage_by_id(passage.id, actor=actor)
1461
+
1440
1462
  # First, if the agent is in the in-memory cache we should remove it
1441
1463
  # List of {'user_id': user_id, 'agent_id': agent_id, 'agent': agent_obj} dicts
1442
1464
  try:
@@ -1479,7 +1501,7 @@ class SyncServer(Server):
1479
1501
  self.ms.delete_api_key(api_key=api_key)
1480
1502
  return api_key_obj
1481
1503
 
1482
- def delete_source(self, source_id: str, actor: User):
1504
+ def delete_source(self, source_id: str, actor: PydanticUser):
1483
1505
  """Delete a data source"""
1484
1506
  self.source_manager.delete_source(source_id=source_id, actor=actor)
1485
1507
 
@@ -1489,7 +1511,7 @@ class SyncServer(Server):
1489
1511
 
1490
1512
  # TODO: delete data from agent passage stores (?)
1491
1513
 
1492
- def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: User) -> Job:
1514
+ def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: PydanticUser) -> Job:
1493
1515
 
1494
1516
  # update job
1495
1517
  job = self.job_manager.get_job_by_id(job_id, actor=actor)
@@ -1516,6 +1538,7 @@ class SyncServer(Server):
1516
1538
  user_id: str,
1517
1539
  connector: DataConnector,
1518
1540
  source_name: str,
1541
+ agent_id: Optional[str] = None,
1519
1542
  ) -> Tuple[int, int]:
1520
1543
  """Load data from a DataConnector into a source for a specified user_id"""
1521
1544
  # TODO: this should be implemented as a batch job or at least async, since it may take a long time
@@ -1530,14 +1553,13 @@ class SyncServer(Server):
1530
1553
  passage_store = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
1531
1554
 
1532
1555
  # load data into the document store
1533
- passage_count, document_count = load_data(connector, source, passage_store, self.source_manager, actor=user)
1556
+ passage_count, document_count = load_data(connector, source, passage_store, self.source_manager, actor=user, agent_id=agent_id)
1534
1557
  return passage_count, document_count
1535
1558
 
1536
1559
  def attach_source_to_agent(
1537
1560
  self,
1538
1561
  user_id: str,
1539
1562
  agent_id: str,
1540
- # source_id: str,
1541
1563
  source_id: Optional[str] = None,
1542
1564
  source_name: Optional[str] = None,
1543
1565
  ) -> Source:
@@ -1549,15 +1571,14 @@ class SyncServer(Server):
1549
1571
  data_source = self.source_manager.get_source_by_name(source_name=source_name, actor=user)
1550
1572
  else:
1551
1573
  raise ValueError(f"Need to provide at least source_id or source_name to find the source.")
1552
- # get connection to data source storage
1553
- source_connector = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
1574
+
1554
1575
  assert data_source, f"Data source with id={source_id} or name={source_name} does not exist"
1555
1576
 
1556
1577
  # load agent
1557
1578
  agent = self.load_agent(agent_id=agent_id)
1558
1579
 
1559
1580
  # attach source to agent
1560
- agent.attach_source(data_source.id, source_connector, self.ms)
1581
+ agent.attach_source(user=user, source_id=data_source.id, source_manager=self.source_manager, ms=self.ms)
1561
1582
 
1562
1583
  return data_source
1563
1584
 
@@ -1580,8 +1601,7 @@ class SyncServer(Server):
1580
1601
 
1581
1602
  # delete all Passage objects with source_id==source_id from agent's archival memory
1582
1603
  agent = self.load_agent(agent_id=agent_id)
1583
- archival_memory = agent.archival_memory
1584
- archival_memory.storage.delete({"source_id": source_id})
1604
+ agent.passage_manager.delete_passages(actor=user, limit=100, source_id=source_id)
1585
1605
 
1586
1606
  # delete agent-source mapping
1587
1607
  self.ms.detach_source(agent_id=agent_id, source_id=source_id)
@@ -1595,11 +1615,11 @@ class SyncServer(Server):
1595
1615
 
1596
1616
  return [self.source_manager.get_source_by_id(source_id=id) for id in source_ids]
1597
1617
 
1598
- def list_data_source_passages(self, user_id: str, source_id: str) -> List[Passage]:
1618
+ def list_data_source_passages(self, user_id: str, source_id: str) -> List[PydanticPassage]:
1599
1619
  warnings.warn("list_data_source_passages is not yet implemented, returning empty list.", category=UserWarning)
1600
1620
  return []
1601
1621
 
1602
- def list_all_sources(self, actor: User) -> List[Source]:
1622
+ def list_all_sources(self, actor: PydanticUser) -> List[Source]:
1603
1623
  """List all sources (w/ extra metadata) belonging to a user"""
1604
1624
 
1605
1625
  sources = self.source_manager.list_sources(actor=actor)
@@ -1639,7 +1659,7 @@ class SyncServer(Server):
1639
1659
 
1640
1660
  return sources_with_metadata
1641
1661
 
1642
- def add_default_external_tools(self, actor: User) -> bool:
1662
+ def add_default_external_tools(self, actor: PydanticUser) -> bool:
1643
1663
  """Add default langchain tools. Return true if successful, false otherwise."""
1644
1664
  success = True
1645
1665
  tool_creates = ToolCreate.load_default_langchain_tools()
@@ -1696,7 +1716,7 @@ class SyncServer(Server):
1696
1716
  save_agent(letta_agent, self.ms)
1697
1717
  return response
1698
1718
 
1699
- def get_user_or_default(self, user_id: Optional[str]) -> User:
1719
+ def get_user_or_default(self, user_id: Optional[str]) -> PydanticUser:
1700
1720
  """Get the user object for user_id if it exists, otherwise return the default user object"""
1701
1721
  if user_id is None:
1702
1722
  user_id = self.user_manager.DEFAULT_USER_ID
@@ -1877,6 +1897,8 @@ class SyncServer(Server):
1877
1897
  date=get_utc_time(),
1878
1898
  status="success",
1879
1899
  function_return=function_response,
1900
+ stdout=sandbox_run_result.stdout,
1901
+ stderr=sandbox_run_result.stderr,
1880
1902
  )
1881
1903
  except Exception as e:
1882
1904
  # same as agent.py
@@ -1892,6 +1914,8 @@ class SyncServer(Server):
1892
1914
  date=get_utc_time(),
1893
1915
  status="error",
1894
1916
  function_return=error_msg,
1917
+ stdout=[''],
1918
+ stderr=[traceback.format_exc()],
1895
1919
  )
1896
1920
 
1897
1921
  # Composio wrappers