letta-nightly 0.5.4.dev20241126104249__py3-none-any.whl → 0.5.4.dev20241128000451__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 (46) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +102 -140
  3. letta/agent_store/chroma.py +2 -0
  4. letta/cli/cli.py +3 -5
  5. letta/client/client.py +360 -117
  6. letta/config.py +2 -2
  7. letta/constants.py +5 -0
  8. letta/errors.py +12 -0
  9. letta/functions/function_sets/base.py +38 -1
  10. letta/functions/functions.py +4 -6
  11. letta/functions/schema_generator.py +6 -5
  12. letta/helpers/tool_rule_solver.py +6 -5
  13. letta/main.py +1 -1
  14. letta/metadata.py +45 -42
  15. letta/o1_agent.py +1 -4
  16. letta/orm/block.py +2 -1
  17. letta/orm/blocks_agents.py +4 -1
  18. letta/orm/sqlalchemy_base.py +13 -0
  19. letta/persistence_manager.py +1 -0
  20. letta/schemas/agent.py +57 -52
  21. letta/schemas/block.py +70 -26
  22. letta/schemas/enums.py +14 -0
  23. letta/schemas/letta_base.py +1 -1
  24. letta/schemas/letta_request.py +11 -23
  25. letta/schemas/letta_response.py +1 -2
  26. letta/schemas/memory.py +31 -100
  27. letta/schemas/message.py +3 -3
  28. letta/schemas/tool_rule.py +13 -5
  29. letta/server/rest_api/interface.py +12 -19
  30. letta/server/rest_api/routers/openai/assistants/threads.py +2 -3
  31. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +0 -2
  32. letta/server/rest_api/routers/v1/agents.py +100 -94
  33. letta/server/rest_api/routers/v1/blocks.py +50 -5
  34. letta/server/rest_api/routers/v1/tools.py +14 -3
  35. letta/server/server.py +246 -460
  36. letta/server/static_files/assets/index-9fa459a2.js +1 -1
  37. letta/services/block_manager.py +23 -4
  38. letta/services/blocks_agents_manager.py +23 -1
  39. letta/services/per_agent_lock_manager.py +18 -0
  40. letta/services/tool_execution_sandbox.py +1 -1
  41. letta/services/tool_manager.py +2 -1
  42. {letta_nightly-0.5.4.dev20241126104249.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/METADATA +1 -1
  43. {letta_nightly-0.5.4.dev20241126104249.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/RECORD +46 -45
  44. {letta_nightly-0.5.4.dev20241126104249.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/LICENSE +0 -0
  45. {letta_nightly-0.5.4.dev20241126104249.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/WHEEL +0 -0
  46. {letta_nightly-0.5.4.dev20241126104249.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/entry_points.txt +0 -0
letta/server/server.py CHANGED
@@ -3,6 +3,7 @@ import os
3
3
  import traceback
4
4
  import warnings
5
5
  from abc import abstractmethod
6
+ from asyncio import Lock
6
7
  from datetime import datetime
7
8
  from typing import Callable, Dict, List, Optional, Tuple, Union
8
9
 
@@ -17,24 +18,10 @@ from letta.agent_store.storage import StorageConnector, TableType
17
18
  from letta.credentials import LettaCredentials
18
19
  from letta.data_sources.connectors import DataConnector, load_data
19
20
 
20
- # from letta.data_types import (
21
- # AgentState,
22
- # EmbeddingConfig,
23
- # LLMConfig,
24
- # Message,
25
- # Preset,
26
- # Source,
27
- # Token,
28
- # User,
29
- # )
30
- from letta.functions.functions import generate_schema, parse_source_code
31
- from letta.functions.schema_generator import generate_schema
32
-
33
21
  # TODO use custom interface
34
22
  from letta.interface import AgentInterface # abstract
35
23
  from letta.interface import CLIInterface # for printing to terminal
36
24
  from letta.log import get_logger
37
- from letta.memory import get_memory_functions
38
25
  from letta.metadata import MetadataStore
39
26
  from letta.o1_agent import O1Agent
40
27
  from letta.orm import Base
@@ -53,8 +40,15 @@ from letta.providers import (
53
40
  VLLMChatCompletionsProvider,
54
41
  VLLMCompletionsProvider,
55
42
  )
56
- from letta.schemas.agent import AgentState, AgentType, CreateAgent, UpdateAgentState
43
+ from letta.schemas.agent import (
44
+ AgentState,
45
+ AgentType,
46
+ CreateAgent,
47
+ PersistedAgentState,
48
+ UpdateAgentState,
49
+ )
57
50
  from letta.schemas.api_key import APIKey, APIKeyCreate
51
+ from letta.schemas.block import Block, BlockUpdate
58
52
  from letta.schemas.embedding_config import EmbeddingConfig
59
53
 
60
54
  # openai schemas
@@ -79,15 +73,13 @@ from letta.services.agents_tags_manager import AgentsTagsManager
79
73
  from letta.services.block_manager import BlockManager
80
74
  from letta.services.blocks_agents_manager import BlocksAgentsManager
81
75
  from letta.services.organization_manager import OrganizationManager
76
+ from letta.services.per_agent_lock_manager import PerAgentLockManager
82
77
  from letta.services.sandbox_config_manager import SandboxConfigManager
83
78
  from letta.services.source_manager import SourceManager
84
79
  from letta.services.tool_manager import ToolManager
85
80
  from letta.services.user_manager import UserManager
86
81
  from letta.utils import create_random_username, json_dumps, json_loads
87
82
 
88
- # from letta.llm_api_tools import openai_get_model_list, azure_openai_get_model_list, smart_urljoin
89
-
90
-
91
83
  logger = get_logger(__name__)
92
84
 
93
85
 
@@ -127,10 +119,11 @@ class Server(object):
127
119
  @abstractmethod
128
120
  def create_agent(
129
121
  self,
130
- user_id: str,
131
- agent_config: Union[dict, AgentState],
132
- interface: Union[AgentInterface, None],
133
- ) -> str:
122
+ request: CreateAgent,
123
+ actor: User,
124
+ # interface
125
+ interface: Union[AgentInterface, None] = None,
126
+ ) -> AgentState:
134
127
  """Create a new agent using a config"""
135
128
  raise NotImplementedError
136
129
 
@@ -231,6 +224,9 @@ class SyncServer(Server):
231
224
 
232
225
  self.credentials = LettaCredentials.load()
233
226
 
227
+ # Locks
228
+ self.send_message_lock = Lock()
229
+
234
230
  # Initialize the metadata store
235
231
  config = LettaConfig.load()
236
232
  if settings.letta_pg_uri_no_default:
@@ -249,8 +245,11 @@ class SyncServer(Server):
249
245
  self.block_manager = BlockManager()
250
246
  self.source_manager = SourceManager()
251
247
  self.agents_tags_manager = AgentsTagsManager()
252
- self.blocks_agents_manager = BlocksAgentsManager()
253
248
  self.sandbox_config_manager = SandboxConfigManager(tool_settings)
249
+ self.blocks_agents_manager = BlocksAgentsManager()
250
+
251
+ # Managers that interface with parallelism
252
+ self.per_agent_lock_manager = PerAgentLockManager()
254
253
 
255
254
  # Make default user and org
256
255
  if init_with_default_org_and_user:
@@ -364,92 +363,23 @@ class SyncServer(Server):
364
363
  }
365
364
  )
366
365
 
367
- def _load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
368
- """Loads a saved agent into memory (if it doesn't exist, throw an error)"""
369
- assert isinstance(agent_id, str), agent_id
370
- user_id = actor.id
371
-
372
- # If an interface isn't specified, use the default
373
- if interface is None:
374
- interface = self.default_interface_factory()
375
-
376
- try:
377
- logger.debug(f"Grabbing agent user_id={user_id} agent_id={agent_id} from database")
378
- agent_state = self.ms.get_agent(agent_id=agent_id, user_id=user_id)
379
- if not agent_state:
380
- logger.exception(f"agent_id {agent_id} does not exist")
381
- raise ValueError(f"agent_id {agent_id} does not exist")
382
-
383
- # Instantiate an agent object using the state retrieved
384
- logger.debug(f"Creating an agent object")
385
- tool_objs = []
386
- for name in agent_state.tools:
387
- # TODO: This should be a hard failure, but for migration reasons, we patch it for now
388
- tool_obj = self.tool_manager.get_tool_by_name(tool_name=name, actor=actor)
389
- if tool_obj:
390
- tool_obj = self.tool_manager.get_tool_by_name(tool_name=name, actor=actor)
391
- tool_objs.append(tool_obj)
392
- else:
393
- warnings.warn(f"Tried to retrieve a tool with name {name} from the agent_state, but does not exist in tool db.")
394
-
395
- # set agent_state tools to only the names of the available tools
396
- agent_state.tools = [t.name for t in tool_objs]
366
+ def load_agent(self, agent_id: str, interface: Union[AgentInterface, None] = None) -> Agent:
367
+ """Updated method to load agents from persisted storage"""
368
+ agent_state = self.get_agent(agent_id=agent_id)
369
+ actor = self.user_manager.get_user_by_id(user_id=agent_state.user_id)
397
370
 
398
- # Make sure the memory is a memory object
399
- assert isinstance(agent_state.memory, Memory)
400
-
401
- if agent_state.agent_type == AgentType.memgpt_agent:
402
- letta_agent = Agent(agent_state=agent_state, interface=interface, tools=tool_objs, user=actor)
403
- elif agent_state.agent_type == AgentType.o1_agent:
404
- letta_agent = O1Agent(agent_state=agent_state, interface=interface, tools=tool_objs, user=actor)
405
- else:
406
- raise NotImplementedError("Not a supported agent type")
407
-
408
- # Add the agent to the in-memory store and return its reference
409
- logger.debug(f"Adding agent to the agent cache: user_id={user_id}, agent_id={agent_id}")
410
- self._add_agent(user_id=user_id, agent_id=agent_id, agent_obj=letta_agent)
411
- return letta_agent
412
-
413
- except Exception as e:
414
- logger.exception(f"Error occurred while trying to get agent {agent_id}:\n{e}")
415
- raise
416
-
417
- def _get_or_load_agent(self, agent_id: str, caching: bool = True) -> Agent:
418
- """Check if the agent is in-memory, then load"""
419
-
420
- # Gets the agent state
421
- agent_state = self.ms.get_agent(agent_id=agent_id)
422
- if not agent_state:
423
- raise ValueError(f"Agent does not exist")
424
- user_id = agent_state.user_id
425
- actor = self.user_manager.get_user_by_id(user_id)
426
-
427
- logger.debug(f"Checking for agent user_id={user_id} agent_id={agent_id}")
428
- if caching:
429
- # TODO: consider disabling loading cached agents due to potential concurrency issues
430
- letta_agent = self._get_agent(user_id=user_id, agent_id=agent_id)
431
- if not letta_agent:
432
- logger.debug(f"Agent not loaded, loading agent user_id={user_id} agent_id={agent_id}")
433
- letta_agent = self._load_agent(agent_id=agent_id, actor=actor)
371
+ interface = interface or self.default_interface_factory()
372
+ if agent_state.agent_type == AgentType.memgpt_agent:
373
+ return Agent(agent_state=agent_state, interface=interface, user=actor)
434
374
  else:
435
- # This breaks unit tests in test_local_client.py
436
- letta_agent = self._load_agent(agent_id=agent_id, actor=actor)
437
-
438
- # letta_agent = self._get_agent(user_id=user_id, agent_id=agent_id)
439
- # if not letta_agent:
440
- # logger.debug(f"Agent not loaded, loading agent user_id={user_id} agent_id={agent_id}")
441
-
442
- # NOTE: no longer caching, always forcing a lot from the database
443
- # Loads the agent objects
444
- # letta_agent = self._load_agent(agent_id=agent_id, actor=actor)
445
-
446
- return letta_agent
375
+ return O1Agent(agent_state=agent_state, interface=interface, user=actor)
447
376
 
448
377
  def _step(
449
378
  self,
450
379
  user_id: str,
451
380
  agent_id: str,
452
381
  input_messages: Union[Message, List[Message]],
382
+ interface: Union[AgentInterface, None] = None, # needed to getting responses
453
383
  # timestamp: Optional[datetime],
454
384
  ) -> LettaUsageStatistics:
455
385
  """Send the input message through the agent"""
@@ -465,7 +395,8 @@ class SyncServer(Server):
465
395
  try:
466
396
 
467
397
  # Get the agent object (loaded in memory)
468
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
398
+ # letta_agent = self._get_or_load_agent(agent_id=agent_id)
399
+ letta_agent = self.load_agent(agent_id=agent_id, interface=interface)
469
400
  if letta_agent is None:
470
401
  raise KeyError(f"Agent (user={user_id}, agent={agent_id}) is not loaded")
471
402
 
@@ -482,6 +413,9 @@ class SyncServer(Server):
482
413
  skip_verify=True,
483
414
  )
484
415
 
416
+ # save agent after step
417
+ save_agent(letta_agent, self.ms)
418
+
485
419
  except Exception as e:
486
420
  logger.error(f"Error in server._step: {e}")
487
421
  print(traceback.print_exc())
@@ -499,7 +433,7 @@ class SyncServer(Server):
499
433
  logger.debug(f"Got command: {command}")
500
434
 
501
435
  # Get the agent object (loaded in memory)
502
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
436
+ letta_agent = self.load_agent(agent_id=agent_id)
503
437
  usage = None
504
438
 
505
439
  if command.lower() == "exit":
@@ -536,7 +470,7 @@ class SyncServer(Server):
536
470
  elif command.lower() == "memory":
537
471
  ret_str = (
538
472
  f"\nDumping memory contents:\n"
539
- + f"\n{str(letta_agent.memory)}"
473
+ + f"\n{str(letta_agent.agent_state.memory)}"
540
474
  + f"\n{str(letta_agent.persistence_manager.archival_memory)}"
541
475
  + f"\n{str(letta_agent.persistence_manager.recall_memory)}"
542
476
  )
@@ -728,6 +662,7 @@ class SyncServer(Server):
728
662
  # whether or not to wrap user and system message as MemGPT-style stringified JSON
729
663
  wrap_user_message: bool = True,
730
664
  wrap_system_message: bool = True,
665
+ interface: Union[AgentInterface, None] = None, # needed to getting responses
731
666
  ) -> LettaUsageStatistics:
732
667
  """Send a list of messages to the agent
733
668
 
@@ -780,7 +715,7 @@ class SyncServer(Server):
780
715
  raise ValueError(f"All messages must be of type Message or MessageCreate, got {[type(message) for message in messages]}")
781
716
 
782
717
  # Run the agent state forward
783
- return self._step(user_id=user_id, agent_id=agent_id, input_messages=message_objects)
718
+ return self._step(user_id=user_id, agent_id=agent_id, input_messages=message_objects, interface=interface)
784
719
 
785
720
  # @LockingServer.agent_lock_decorator
786
721
  def run_command(self, user_id: str, agent_id: str, command: str) -> LettaUsageStatistics:
@@ -828,129 +763,109 @@ class SyncServer(Server):
828
763
  else:
829
764
  raise ValueError(f"Invalid agent type: {request.agent_type}")
830
765
 
766
+ # create blocks (note: cannot be linked into the agent_id is created)
767
+ blocks = []
768
+ for create_block in request.memory_blocks:
769
+ block = self.block_manager.create_or_update_block(Block(**create_block.model_dump()), actor=actor)
770
+ blocks.append(block)
771
+
772
+ # get tools + only add if they exist
773
+ tool_objs = []
774
+ if request.tools:
775
+ for tool_name in request.tools:
776
+ tool_obj = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
777
+ if tool_obj:
778
+ tool_objs.append(tool_obj)
779
+ else:
780
+ warnings.warn(f"Attempted to add a nonexistent tool {tool_name} to agent {request.name}, skipping.")
781
+ # reset the request.tools to only valid tools
782
+ request.tools = [t.name for t in tool_objs]
783
+
784
+ # get the user
831
785
  logger.debug(f"Attempting to find user: {user_id}")
832
786
  user = self.user_manager.get_user_by_id(user_id=user_id)
833
787
  if not user:
834
788
  raise ValueError(f"cannot find user with associated client id: {user_id}")
835
789
 
836
- try:
837
- # model configuration
838
- llm_config = request.llm_config
839
- embedding_config = request.embedding_config
840
-
841
- # get tools + only add if they exist
842
- tool_objs = []
843
- if request.tools:
844
- for tool_name in request.tools:
845
- tool_obj = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
846
- if tool_obj:
847
- tool_objs.append(tool_obj)
848
- else:
849
- warnings.warn(f"Attempted to add a nonexistent tool {tool_name} to agent {request.name}, skipping.")
850
- # reset the request.tools to only valid tools
851
- request.tools = [t.name for t in tool_objs]
852
-
853
- assert request.memory is not None
854
- memory_functions = get_memory_functions(request.memory)
855
- for func_name, func in memory_functions.items():
856
-
857
- if request.tools and func_name in request.tools:
858
- # tool already added
859
- continue
860
- source_code = parse_source_code(func)
861
- # memory functions are not terminal
862
- json_schema = generate_schema(func, name=func_name)
863
- source_type = "python"
864
- tags = ["memory", "memgpt-base"]
865
- tool = self.tool_manager.create_or_update_tool(
866
- Tool(
867
- source_code=source_code,
868
- source_type=source_type,
869
- tags=tags,
870
- json_schema=json_schema,
871
- ),
872
- actor=actor,
873
- )
874
- tool_objs.append(tool)
875
- if not request.tools:
876
- request.tools = []
877
- request.tools.append(tool.name)
878
-
879
- # TODO: save the agent state
880
- agent_state = AgentState(
881
- name=request.name,
882
- user_id=user_id,
883
- tools=request.tools if request.tools else [],
884
- tool_rules=request.tool_rules if request.tool_rules else [],
885
- agent_type=request.agent_type or AgentType.memgpt_agent,
886
- llm_config=llm_config,
887
- embedding_config=embedding_config,
888
- system=request.system,
889
- memory=request.memory,
890
- description=request.description,
891
- metadata_=request.metadata_,
892
- tags=request.tags,
893
- )
894
- if request.agent_type == AgentType.memgpt_agent:
895
- agent = Agent(
896
- interface=interface,
897
- agent_state=agent_state,
898
- tools=tool_objs,
899
- # gpt-3.5-turbo tends to omit inner monologue, relax this requirement for now
900
- first_message_verify_mono=(
901
- True if (llm_config and llm_config.model is not None and "gpt-4" in llm_config.model) else False
902
- ),
903
- user=actor,
904
- initial_message_sequence=request.initial_message_sequence,
905
- )
906
- elif request.agent_type == AgentType.o1_agent:
907
- agent = O1Agent(
908
- interface=interface,
909
- agent_state=agent_state,
910
- tools=tool_objs,
911
- # gpt-3.5-turbo tends to omit inner monologue, relax this requirement for now
912
- first_message_verify_mono=(
913
- True if (llm_config and llm_config.model is not None and "gpt-4" in llm_config.model) else False
914
- ),
915
- user=actor,
916
- )
917
- # rebuilding agent memory on agent create in case shared memory blocks
918
- # were specified in the new agent's memory config. we're doing this for two reasons:
919
- # 1. if only the ID of the shared memory block was specified, we can fetch its most recent value
920
- # 2. if the shared block state changed since this agent initialization started, we can be sure to have the latest value
921
- agent.rebuild_memory(force=True, ms=self.ms)
922
- # FIXME: this is a hacky way to get the system prompts injected into agent into the DB
923
- # self.ms.update_agent(agent.agent_state)
924
- except Exception as e:
925
- logger.exception(e)
926
- try:
927
- if agent:
928
- self.ms.delete_agent(agent_id=agent.agent_state.id)
929
- except Exception as delete_e:
930
- logger.exception(f"Failed to delete_agent:\n{delete_e}")
931
- raise e
790
+ # TODO: create the message objects (NOTE: do this after we migrate to `CreateMessage`)
932
791
 
933
- # save agent
934
- save_agent(agent, self.ms)
935
- logger.debug(f"Created new agent from config: {agent}")
792
+ # created and persist the agent state in the DB
793
+ agent_state = PersistedAgentState(
794
+ name=request.name,
795
+ user_id=user_id,
796
+ tool_names=request.tools if request.tools else [],
797
+ tool_rules=request.tool_rules,
798
+ agent_type=request.agent_type or AgentType.memgpt_agent,
799
+ llm_config=request.llm_config,
800
+ embedding_config=request.embedding_config,
801
+ system=request.system,
802
+ # other metadata
803
+ description=request.description,
804
+ metadata_=request.metadata_,
805
+ )
806
+ # TODO: move this to agent ORM
807
+ # this saves the agent ID and state into the DB
808
+ self.ms.create_agent(agent_state)
936
809
 
937
- # TODO: move this into save_agent. save_agent should be moved to server.py
810
+ # Note: mappings (e.g. tags, blocks) are created after the agent is persisted
811
+ # TODO: add source mappings here as well
812
+
813
+ # create the tags
938
814
  if request.tags:
939
815
  for tag in request.tags:
940
- self.agents_tags_manager.add_tag_to_agent(agent_id=agent.agent_state.id, tag=tag, actor=actor)
816
+ self.agents_tags_manager.add_tag_to_agent(agent_id=agent_state.id, tag=tag, actor=actor)
817
+
818
+ # create block mappins (now that agent is persisted)
819
+ for block in blocks:
820
+ # this links the created block to the agent
821
+ self.blocks_agents_manager.add_block_to_agent(block_id=block.id, agent_id=agent_state.id, block_label=block.label)
822
+
823
+ in_memory_agent_state = self.get_agent(agent_state.id)
824
+ return in_memory_agent_state
825
+
826
+ def get_agent(self, agent_id: str) -> AgentState:
827
+ """
828
+ Retrieve the full agent state from the DB.
829
+ This gathers data accross multiple tables to provide the full state of an agent, which is passed into the `Agent` object for creation.
830
+ """
831
+
832
+ # get data persisted from the DB
833
+ agent_state = self.ms.get_agent(agent_id=agent_id)
834
+ if agent_state is None:
835
+ # agent does not exist
836
+ return None
837
+ user = self.user_manager.get_user_by_id(user_id=agent_state.user_id)
941
838
 
942
- assert isinstance(agent.agent_state.memory, Memory), f"Invalid memory type: {type(agent_state.memory)}"
839
+ # construct the in-memory, full agent state - this gather data stored in different tables but that needs to be passed to `Agent`
840
+ # we also return this data to the user to provide all the state related to an agent
943
841
 
944
- # TODO: remove (hacky)
945
- agent.agent_state.tags = self.agents_tags_manager.get_tags_for_agent(agent_id=agent.agent_state.id, actor=actor)
842
+ # get `Memory` object by getting the linked block IDs and fetching the blocks, then putting that into a `Memory` object
843
+ # this is the "in memory" representation of the in-context memory
844
+ block_ids = self.blocks_agents_manager.list_block_ids_for_agent(agent_id=agent_id)
845
+ blocks = []
846
+ for block_id in block_ids:
847
+ block = self.block_manager.get_block_by_id(block_id=block_id, actor=user)
848
+ assert block, f"Block with ID {block_id} does not exist"
849
+ blocks.append(block)
850
+ memory = Memory(blocks=blocks)
946
851
 
947
- return agent.agent_state
852
+ # get `Tool` objects
853
+ tools = [self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=user) for tool_name in agent_state.tool_names]
854
+
855
+ # get `Source` objects
856
+ sources = self.list_attached_sources(agent_id=agent_id)
857
+
858
+ # get the tags
859
+ tags = self.agents_tags_manager.get_tags_for_agent(agent_id=agent_id, actor=user)
860
+
861
+ # return the full agent state - this contains all data needed to recreate the agent
862
+ return AgentState(**agent_state.model_dump(), memory=memory, tools=tools, sources=sources, tags=tags)
948
863
 
949
864
  def update_agent(
950
865
  self,
951
866
  request: UpdateAgentState,
952
867
  actor: User,
953
- ):
868
+ ) -> AgentState:
954
869
  """Update the agents core memory block, return the new state"""
955
870
  try:
956
871
  self.user_manager.get_user_by_id(user_id=actor.id)
@@ -961,13 +876,7 @@ class SyncServer(Server):
961
876
  raise ValueError(f"Agent agent_id={request.id} does not exist")
962
877
 
963
878
  # Get the agent object (loaded in memory)
964
- letta_agent = self._get_or_load_agent(agent_id=request.id)
965
-
966
- # update the core memory of the agent
967
- if request.memory:
968
- assert isinstance(request.memory, Memory), type(request.memory)
969
- new_memory_contents = request.memory.to_flat_dict()
970
- _ = self.update_agent_core_memory(user_id=actor.id, agent_id=request.id, new_memory_contents=new_memory_contents)
879
+ letta_agent = self.load_agent(agent_id=request.id)
971
880
 
972
881
  # update the system prompt
973
882
  if request.system:
@@ -981,13 +890,13 @@ class SyncServer(Server):
981
890
  letta_agent.set_message_buffer(message_ids=request.message_ids)
982
891
 
983
892
  # tools
984
- if request.tools:
893
+ if request.tool_names:
985
894
  # Replace tools and also re-link
986
895
 
987
896
  # (1) get tools + make sure they exist
988
897
  # Current and target tools as sets of tool names
989
- current_tools = set(letta_agent.agent_state.tools)
990
- target_tools = set(request.tools)
898
+ current_tools = set(letta_agent.agent_state.tool_names)
899
+ target_tools = set(request.tool_names)
991
900
 
992
901
  # Calculate tools to add and remove
993
902
  tools_to_add = target_tools - current_tools
@@ -1004,7 +913,7 @@ class SyncServer(Server):
1004
913
  self.add_tool_to_agent(agent_id=request.id, tool_id=tool.id, user_id=actor.id)
1005
914
 
1006
915
  # reload agent
1007
- letta_agent = self._get_or_load_agent(agent_id=request.id)
916
+ letta_agent = self.load_agent(agent_id=request.id)
1008
917
 
1009
918
  # configs
1010
919
  if request.llm_config:
@@ -1032,7 +941,6 @@ class SyncServer(Server):
1032
941
  self.agents_tags_manager.delete_tag_from_agent(agent_id=letta_agent.agent_state.id, tag=tag, actor=actor)
1033
942
 
1034
943
  # save the agent
1035
- assert isinstance(letta_agent.memory, Memory)
1036
944
  save_agent(letta_agent, self.ms)
1037
945
  # TODO: probably reload the agent somehow?
1038
946
  return letta_agent.agent_state
@@ -1045,8 +953,8 @@ class SyncServer(Server):
1045
953
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1046
954
 
1047
955
  # Get the agent object (loaded in memory)
1048
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1049
- return letta_agent.tools
956
+ letta_agent = self.load_agent(agent_id=agent_id)
957
+ return letta_agent.agent_state.tools
1050
958
 
1051
959
  def add_tool_to_agent(
1052
960
  self,
@@ -1064,7 +972,7 @@ class SyncServer(Server):
1064
972
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1065
973
 
1066
974
  # Get the agent object (loaded in memory)
1067
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
975
+ letta_agent = self.load_agent(agent_id=agent_id)
1068
976
 
1069
977
  # Get all the tool objects from the request
1070
978
  tool_objs = []
@@ -1072,7 +980,7 @@ class SyncServer(Server):
1072
980
  assert tool_obj, f"Tool with id={tool_id} does not exist"
1073
981
  tool_objs.append(tool_obj)
1074
982
 
1075
- for tool in letta_agent.tools:
983
+ for tool in letta_agent.agent_state.tools:
1076
984
  tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id, actor=user)
1077
985
  assert tool_obj, f"Tool with id={tool.id} does not exist"
1078
986
 
@@ -1081,7 +989,7 @@ class SyncServer(Server):
1081
989
  tool_objs.append(tool_obj)
1082
990
 
1083
991
  # replace the list of tool names ("ids") inside the agent state
1084
- letta_agent.agent_state.tools = [tool.name for tool in tool_objs]
992
+ letta_agent.agent_state.tool_names = [tool.name for tool in tool_objs]
1085
993
 
1086
994
  # then attempt to link the tools modules
1087
995
  letta_agent.link_tools(tool_objs)
@@ -1106,11 +1014,11 @@ class SyncServer(Server):
1106
1014
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1107
1015
 
1108
1016
  # Get the agent object (loaded in memory)
1109
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1017
+ letta_agent = self.load_agent(agent_id=agent_id)
1110
1018
 
1111
1019
  # Get all the tool_objs
1112
1020
  tool_objs = []
1113
- for tool in letta_agent.tools:
1021
+ for tool in letta_agent.agent_state.tools:
1114
1022
  tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id, actor=user)
1115
1023
  assert tool_obj, f"Tool with id={tool.id} does not exist"
1116
1024
 
@@ -1119,7 +1027,7 @@ class SyncServer(Server):
1119
1027
  tool_objs.append(tool_obj)
1120
1028
 
1121
1029
  # replace the list of tool names ("ids") inside the agent state
1122
- letta_agent.agent_state.tools = [tool.name for tool in tool_objs]
1030
+ letta_agent.agent_state.tool_names = [tool.name for tool in tool_objs]
1123
1031
 
1124
1032
  # then attempt to link the tools modules
1125
1033
  letta_agent.link_tools(tool_objs)
@@ -1128,18 +1036,9 @@ class SyncServer(Server):
1128
1036
  save_agent(letta_agent, self.ms)
1129
1037
  return letta_agent.agent_state
1130
1038
 
1131
- def _agent_state_to_config(self, agent_state: AgentState) -> dict:
1132
- """Convert AgentState to a dict for a JSON response"""
1133
- assert agent_state is not None
1134
-
1135
- agent_config = {
1136
- "id": agent_state.id,
1137
- "name": agent_state.name,
1138
- "human": agent_state._metadata.get("human", None),
1139
- "persona": agent_state._metadata.get("persona", None),
1140
- "created_at": agent_state.created_at.isoformat(),
1141
- }
1142
- return agent_config
1039
+ def get_agent_state(self, user_id: str, agent_id: str) -> AgentState:
1040
+ # TODO: duplicate, remove
1041
+ return self.get_agent(agent_id=agent_id)
1143
1042
 
1144
1043
  def list_agents(self, user_id: str, tags: Optional[List[str]] = None) -> List[AgentState]:
1145
1044
  """List all available agents to a user"""
@@ -1147,13 +1046,13 @@ class SyncServer(Server):
1147
1046
 
1148
1047
  if tags is None:
1149
1048
  agents_states = self.ms.list_agents(user_id=user_id)
1150
- return agents_states
1049
+ agent_ids = [agent.id for agent in agents_states]
1151
1050
  else:
1152
1051
  agent_ids = []
1153
1052
  for tag in tags:
1154
1053
  agent_ids += self.agents_tags_manager.get_agents_by_tag(tag=tag, actor=user)
1155
1054
 
1156
- return [self.get_agent_state(user_id=user.id, agent_id=agent_id) for agent_id in agent_ids]
1055
+ return [self.get_agent(agent_id=agent_id) for agent_id in agent_ids]
1157
1056
 
1158
1057
  # convert name->id
1159
1058
 
@@ -1177,34 +1076,34 @@ class SyncServer(Server):
1177
1076
 
1178
1077
  def get_agent_memory(self, agent_id: str) -> Memory:
1179
1078
  """Return the memory of an agent (core memory)"""
1180
- agent = self._get_or_load_agent(agent_id=agent_id)
1181
- return agent.memory
1079
+ agent = self.load_agent(agent_id=agent_id)
1080
+ return agent.agent_state.memory
1182
1081
 
1183
1082
  def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
1184
- agent = self._get_or_load_agent(agent_id=agent_id)
1083
+ agent = self.load_agent(agent_id=agent_id)
1185
1084
  return ArchivalMemorySummary(size=len(agent.persistence_manager.archival_memory))
1186
1085
 
1187
1086
  def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
1188
- agent = self._get_or_load_agent(agent_id=agent_id)
1087
+ agent = self.load_agent(agent_id=agent_id)
1189
1088
  return RecallMemorySummary(size=len(agent.persistence_manager.recall_memory))
1190
1089
 
1191
1090
  def get_in_context_message_ids(self, agent_id: str) -> List[str]:
1192
1091
  """Get the message ids of the in-context messages in the agent's memory"""
1193
1092
  # Get the agent object (loaded in memory)
1194
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1195
- return [m.id for m in letta_agent._messages]
1093
+ agent = self.load_agent(agent_id=agent_id)
1094
+ return [m.id for m in agent._messages]
1196
1095
 
1197
1096
  def get_in_context_messages(self, agent_id: str) -> List[Message]:
1198
1097
  """Get the in-context messages in the agent's memory"""
1199
1098
  # Get the agent object (loaded in memory)
1200
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1201
- return letta_agent._messages
1099
+ agent = self.load_agent(agent_id=agent_id)
1100
+ return agent._messages
1202
1101
 
1203
1102
  def get_agent_message(self, agent_id: str, message_id: str) -> Message:
1204
1103
  """Get a single message from the agent's memory"""
1205
1104
  # Get the agent object (loaded in memory)
1206
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1207
- message = letta_agent.persistence_manager.recall_memory.storage.get(id=message_id)
1105
+ agent = self.load_agent(agent_id=agent_id)
1106
+ message = agent.persistence_manager.recall_memory.storage.get(id=message_id)
1208
1107
  return message
1209
1108
 
1210
1109
  def get_agent_messages(
@@ -1212,11 +1111,10 @@ class SyncServer(Server):
1212
1111
  agent_id: str,
1213
1112
  start: int,
1214
1113
  count: int,
1215
- return_message_object: bool = True,
1216
1114
  ) -> Union[List[Message], List[LettaMessage]]:
1217
1115
  """Paginated query of all messages in agent message queue"""
1218
1116
  # Get the agent object (loaded in memory)
1219
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1117
+ letta_agent = self.load_agent(agent_id=agent_id)
1220
1118
 
1221
1119
  if start < 0 or count < 0:
1222
1120
  raise ValueError("Start and count values should be non-negative")
@@ -1234,10 +1132,6 @@ class SyncServer(Server):
1234
1132
  # Slice the list for pagination
1235
1133
  messages = reversed_messages[start:end_index]
1236
1134
 
1237
- ## Convert to json
1238
- ## Add a tag indicating in-context or not
1239
- # json_messages = [{**record.to_json(), "in_context": True} for record in messages]
1240
-
1241
1135
  else:
1242
1136
  # need to access persistence manager for additional messages
1243
1137
  db_iterator = letta_agent.persistence_manager.recall_memory.storage.get_all_paginated(page_size=count, offset=start)
@@ -1257,9 +1151,6 @@ class SyncServer(Server):
1257
1151
  # for d in json_messages:
1258
1152
  # d["in_context"] = True if str(d["id"]) in in_context_message_ids else False
1259
1153
 
1260
- if not return_message_object:
1261
- messages = [msg for m in messages for msg in m.to_letta_message()]
1262
-
1263
1154
  return messages
1264
1155
 
1265
1156
  def get_agent_archival(self, user_id: str, agent_id: str, start: int, count: int) -> List[Passage]:
@@ -1270,7 +1161,7 @@ class SyncServer(Server):
1270
1161
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1271
1162
 
1272
1163
  # Get the agent object (loaded in memory)
1273
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1164
+ letta_agent = self.load_agent(agent_id=agent_id)
1274
1165
 
1275
1166
  # iterate over records
1276
1167
  db_iterator = letta_agent.persistence_manager.archival_memory.storage.get_all_paginated(page_size=count, offset=start)
@@ -1295,7 +1186,7 @@ class SyncServer(Server):
1295
1186
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1296
1187
 
1297
1188
  # Get the agent object (loaded in memory)
1298
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1189
+ letta_agent = self.load_agent(agent_id=agent_id)
1299
1190
 
1300
1191
  # iterate over recorde
1301
1192
  cursor, records = letta_agent.persistence_manager.archival_memory.storage.get_all_cursor(
@@ -1310,11 +1201,15 @@ class SyncServer(Server):
1310
1201
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1311
1202
 
1312
1203
  # Get the agent object (loaded in memory)
1313
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1204
+ letta_agent = self.load_agent(agent_id=agent_id)
1314
1205
 
1315
1206
  # Insert into archival memory
1316
1207
  passage_ids = letta_agent.persistence_manager.archival_memory.insert(memory_string=memory_contents, return_ids=True)
1317
1208
 
1209
+ # Update the agent
1210
+ # TODO: should this update the system prompt?
1211
+ save_agent(letta_agent, self.ms)
1212
+
1318
1213
  # TODO: this is gross, fix
1319
1214
  return [letta_agent.persistence_manager.archival_memory.storage.get(id=passage_id) for passage_id in passage_ids]
1320
1215
 
@@ -1327,7 +1222,7 @@ class SyncServer(Server):
1327
1222
  # TODO: should return a passage
1328
1223
 
1329
1224
  # Get the agent object (loaded in memory)
1330
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1225
+ letta_agent = self.load_agent(agent_id=agent_id)
1331
1226
 
1332
1227
  # Delete by ID
1333
1228
  # TODO check if it exists first, and throw error if not
@@ -1346,9 +1241,8 @@ class SyncServer(Server):
1346
1241
  order: Optional[str] = "asc",
1347
1242
  reverse: Optional[bool] = False,
1348
1243
  return_message_object: bool = True,
1349
- use_assistant_message: bool = False,
1350
- assistant_message_function_name: str = constants.DEFAULT_MESSAGE_TOOL,
1351
- assistant_message_function_kwarg: str = constants.DEFAULT_MESSAGE_TOOL_KWARG,
1244
+ assistant_message_tool_name: str = constants.DEFAULT_MESSAGE_TOOL,
1245
+ assistant_message_tool_kwarg: str = constants.DEFAULT_MESSAGE_TOOL_KWARG,
1352
1246
  ) -> Union[List[Message], List[LettaMessage]]:
1353
1247
  if self.user_manager.get_user_by_id(user_id=user_id) is None:
1354
1248
  raise ValueError(f"User user_id={user_id} does not exist")
@@ -1356,7 +1250,7 @@ class SyncServer(Server):
1356
1250
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1357
1251
 
1358
1252
  # Get the agent object (loaded in memory)
1359
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1253
+ letta_agent = self.load_agent(agent_id=agent_id)
1360
1254
 
1361
1255
  # iterate over records
1362
1256
  cursor, records = letta_agent.persistence_manager.recall_memory.storage.get_all_cursor(
@@ -1367,50 +1261,19 @@ class SyncServer(Server):
1367
1261
 
1368
1262
  if not return_message_object:
1369
1263
  # If we're GETing messages in reverse, we need to reverse the inner list (generated by to_letta_message)
1370
- if reverse:
1371
- records = [
1372
- msg
1373
- for m in records
1374
- for msg in m.to_letta_message(
1375
- assistant_message=use_assistant_message,
1376
- assistant_message_function_name=assistant_message_function_name,
1377
- assistant_message_function_kwarg=assistant_message_function_kwarg,
1378
- )[::-1]
1379
- ]
1380
- else:
1381
- records = [
1382
- msg
1383
- for m in records
1384
- for msg in m.to_letta_message(
1385
- assistant_message=use_assistant_message,
1386
- assistant_message_function_name=assistant_message_function_name,
1387
- assistant_message_function_kwarg=assistant_message_function_kwarg,
1388
- )
1389
- ]
1390
-
1391
- return records
1392
-
1393
- def get_agent_state(self, user_id: str, agent_id: Optional[str], agent_name: Optional[str] = None) -> Optional[AgentState]:
1394
- """Return the config of an agent"""
1395
- user = self.user_manager.get_user_by_id(user_id=user_id)
1396
- if agent_id:
1397
- if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1398
- return None
1399
- else:
1400
- agent_state = self.ms.get_agent(agent_name=agent_name, user_id=user_id)
1401
- if agent_state is None:
1402
- raise ValueError(f"Agent agent_name={agent_name} does not exist")
1403
- agent_id = agent_state.id
1264
+ records = [
1265
+ msg
1266
+ for m in records
1267
+ for msg in m.to_letta_message(
1268
+ assistant_message_tool_name=assistant_message_tool_name,
1269
+ assistant_message_tool_kwarg=assistant_message_tool_kwarg,
1270
+ )
1271
+ ]
1404
1272
 
1405
- # Get the agent object (loaded in memory)
1406
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1407
- assert isinstance(letta_agent.memory, Memory)
1273
+ if reverse:
1274
+ records = records[::-1]
1408
1275
 
1409
- letta_agent.update_memory_blocks_from_db()
1410
- agent_state = letta_agent.agent_state.model_copy(deep=True)
1411
- # Load the tags in for the agent_state
1412
- agent_state.tags = self.agents_tags_manager.get_tags_for_agent(agent_id=agent_id, actor=user)
1413
- return agent_state
1276
+ return records
1414
1277
 
1415
1278
  def get_server_config(self, include_defaults: bool = False) -> dict:
1416
1279
  """Return the base config"""
@@ -1435,39 +1298,23 @@ class SyncServer(Server):
1435
1298
 
1436
1299
  return response
1437
1300
 
1438
- def update_agent_core_memory(self, user_id: str, agent_id: str, new_memory_contents: dict) -> Memory:
1439
- """Update the agents core memory block, return the new state"""
1440
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1441
- raise ValueError(f"User user_id={user_id} does not exist")
1442
- if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1443
- raise ValueError(f"Agent agent_id={agent_id} does not exist")
1301
+ def update_agent_core_memory(self, user_id: str, agent_id: str, label: str, value: str) -> Memory:
1302
+ """Update the value of a block in the agent's memory"""
1444
1303
 
1445
- # Get the agent object (loaded in memory)
1446
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1447
-
1448
- # old_core_memory = self.get_agent_memory(agent_id=agent_id)
1449
-
1450
- modified = False
1451
- for key, value in new_memory_contents.items():
1452
- if letta_agent.memory.get_block(key) is None:
1453
- # raise ValueError(f"Key {key} not found in agent memory {list(letta_agent.memory.list_block_names())}")
1454
- raise ValueError(f"Key {key} not found in agent memory {str(letta_agent.memory.memory)}")
1455
- if value is None:
1456
- continue
1457
- if letta_agent.memory.get_block(key) != value:
1458
- letta_agent.memory.update_block_value(label=key, value=value) # update agent memory
1459
- modified = True
1460
-
1461
- # If we modified the memory contents, we need to rebuild the memory block inside the system message
1462
- if modified:
1463
- letta_agent.rebuild_memory()
1464
- # letta_agent.rebuild_memory(force=True, ms=self.ms) # This breaks unit tests in test_local_client.py
1465
- # save agent
1466
- save_agent(letta_agent, self.ms)
1304
+ # get the block id
1305
+ block = self.get_agent_block_by_label(user_id=user_id, agent_id=agent_id, label=label)
1306
+ block_id = block.id
1307
+
1308
+ # update the block
1309
+ self.block_manager.update_block(
1310
+ block_id=block_id, block_update=BlockUpdate(value=value), actor=self.user_manager.get_user_by_id(user_id=user_id)
1311
+ )
1467
1312
 
1468
- return self.ms.get_agent(agent_id=agent_id).memory
1313
+ # load agent
1314
+ letta_agent = self.load_agent(agent_id=agent_id)
1315
+ return letta_agent.agent_state.memory
1469
1316
 
1470
- def rename_agent(self, user_id: str, agent_id: str, new_agent_name: str) -> AgentState:
1317
+ def rename_agent(self, user_id: str, agent_id: str, new_agent_name: str) -> PersistedAgentState:
1471
1318
  """Update the name of the agent in the database"""
1472
1319
  if self.user_manager.get_user_by_id(user_id=user_id) is None:
1473
1320
  raise ValueError(f"User user_id={user_id} does not exist")
@@ -1475,7 +1322,7 @@ class SyncServer(Server):
1475
1322
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1476
1323
 
1477
1324
  # Get the agent object (loaded in memory)
1478
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1325
+ letta_agent = self.load_agent(agent_id=agent_id)
1479
1326
 
1480
1327
  current_name = letta_agent.agent_state.name
1481
1328
  if current_name == new_agent_name:
@@ -1497,6 +1344,7 @@ class SyncServer(Server):
1497
1344
  # TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM MODEL
1498
1345
  # TODO: EVENTUALLY WE GET AUTO-DELETES WHEN WE SPECIFY RELATIONSHIPS IN THE ORM
1499
1346
  self.agents_tags_manager.delete_all_tags_from_agent(agent_id=agent_id, actor=actor)
1347
+ self.blocks_agents_manager.remove_all_agent_blocks(agent_id=agent_id)
1500
1348
 
1501
1349
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1502
1350
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1522,7 +1370,7 @@ class SyncServer(Server):
1522
1370
 
1523
1371
  # Next, attempt to delete it from the actual database
1524
1372
  try:
1525
- self.ms.delete_agent(agent_id=agent_id)
1373
+ self.ms.delete_agent(agent_id=agent_id, per_agent_lock_manager=self.per_agent_lock_manager)
1526
1374
  except Exception as e:
1527
1375
  logger.exception(f"Failed to delete agent {agent_id} via ID with:\n{str(e)}")
1528
1376
  raise ValueError(f"Failed to delete agent {agent_id} in database")
@@ -1664,9 +1512,10 @@ class SyncServer(Server):
1664
1512
  raise ValueError(f"Need to provide at least source_id or source_name to find the source.")
1665
1513
  # get connection to data source storage
1666
1514
  source_connector = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
1515
+ assert data_source, f"Data source with id={source_id} or name={source_name} does not exist"
1667
1516
 
1668
1517
  # load agent
1669
- agent = self._get_or_load_agent(agent_id=agent_id)
1518
+ agent = self.load_agent(agent_id=agent_id)
1670
1519
 
1671
1520
  # attach source to agent
1672
1521
  agent.attach_source(data_source.id, source_connector, self.ms)
@@ -1691,7 +1540,7 @@ class SyncServer(Server):
1691
1540
  source_id = source.id
1692
1541
 
1693
1542
  # delete all Passage objects with source_id==source_id from agent's archival memory
1694
- agent = self._get_or_load_agent(agent_id=agent_id)
1543
+ agent = self.load_agent(agent_id=agent_id)
1695
1544
  archival_memory = agent.persistence_manager.archival_memory
1696
1545
  archival_memory.storage.delete({"source_id": source_id})
1697
1546
 
@@ -1770,34 +1619,43 @@ class SyncServer(Server):
1770
1619
  def get_agent_message(self, agent_id: str, message_id: str) -> Optional[Message]:
1771
1620
  """Get a single message from the agent's memory"""
1772
1621
  # Get the agent object (loaded in memory)
1773
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1622
+ letta_agent = self.load_agent(agent_id=agent_id)
1774
1623
  message = letta_agent.persistence_manager.recall_memory.storage.get(id=message_id)
1624
+ save_agent(letta_agent, self.ms)
1775
1625
  return message
1776
1626
 
1777
1627
  def update_agent_message(self, agent_id: str, request: UpdateMessage) -> Message:
1778
1628
  """Update the details of a message associated with an agent"""
1779
1629
 
1780
1630
  # Get the current message
1781
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1782
- return letta_agent.update_message(request=request)
1631
+ letta_agent = self.load_agent(agent_id=agent_id)
1632
+ response = letta_agent.update_message(request=request)
1633
+ save_agent(letta_agent, self.ms)
1634
+ return response
1783
1635
 
1784
1636
  def rewrite_agent_message(self, agent_id: str, new_text: str) -> Message:
1785
1637
 
1786
1638
  # Get the current message
1787
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1788
- return letta_agent.rewrite_message(new_text=new_text)
1639
+ letta_agent = self.load_agent(agent_id=agent_id)
1640
+ response = letta_agent.rewrite_message(new_text=new_text)
1641
+ save_agent(letta_agent, self.ms)
1642
+ return response
1789
1643
 
1790
1644
  def rethink_agent_message(self, agent_id: str, new_thought: str) -> Message:
1791
1645
 
1792
1646
  # Get the current message
1793
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1794
- return letta_agent.rethink_message(new_thought=new_thought)
1647
+ letta_agent = self.load_agent(agent_id=agent_id)
1648
+ response = letta_agent.rethink_message(new_thought=new_thought)
1649
+ save_agent(letta_agent, self.ms)
1650
+ return response
1795
1651
 
1796
1652
  def retry_agent_message(self, agent_id: str) -> List[Message]:
1797
1653
 
1798
1654
  # Get the current message
1799
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1800
- return letta_agent.retry_message()
1655
+ letta_agent = self.load_agent(agent_id=agent_id)
1656
+ response = letta_agent.retry_message()
1657
+ save_agent(letta_agent, self.ms)
1658
+ return response
1801
1659
 
1802
1660
  def get_user_or_default(self, user_id: Optional[str]) -> User:
1803
1661
  """Get the user object for user_id if it exists, otherwise return the default user object"""
@@ -1846,121 +1704,49 @@ class SyncServer(Server):
1846
1704
  agent_id: str,
1847
1705
  ) -> ContextWindowOverview:
1848
1706
  # Get the current message
1849
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1707
+ letta_agent = self.load_agent(agent_id=agent_id)
1850
1708
  return letta_agent.get_context_window()
1851
1709
 
1852
- def update_agent_memory_label(self, user_id: str, agent_id: str, current_block_label: str, new_block_label: str) -> Memory:
1853
- """Update the label of a block in an agent's memory"""
1854
-
1855
- # Get the user
1856
- user = self.user_manager.get_user_by_id(user_id=user_id)
1857
-
1858
- # Link a block to an agent's memory
1859
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1860
- letta_agent.memory.update_block_label(current_label=current_block_label, new_label=new_block_label)
1861
- assert new_block_label in letta_agent.memory.list_block_labels()
1862
- self.block_manager.create_or_update_block(block=letta_agent.memory.get_block(new_block_label), actor=user)
1863
-
1864
- # check that the block was updated
1865
- updated_block = self.block_manager.get_block_by_id(block_id=letta_agent.memory.get_block(new_block_label).id, actor=user)
1866
-
1867
- # Recompile the agent memory
1868
- letta_agent.rebuild_memory(force=True, ms=self.ms)
1869
-
1870
- # save agent
1871
- save_agent(letta_agent, self.ms)
1872
-
1873
- updated_agent = self.ms.get_agent(agent_id=agent_id)
1874
- if updated_agent is None:
1875
- raise ValueError(f"Agent with id {agent_id} not found after linking block")
1876
- assert new_block_label in updated_agent.memory.list_block_labels()
1877
- assert current_block_label not in updated_agent.memory.list_block_labels()
1878
- return updated_agent.memory
1879
-
1880
1710
  def link_block_to_agent_memory(self, user_id: str, agent_id: str, block_id: str) -> Memory:
1881
1711
  """Link a block to an agent's memory"""
1882
-
1883
- # Get the user
1884
- user = self.user_manager.get_user_by_id(user_id=user_id)
1885
-
1886
- # Get the block first
1887
- block = self.block_manager.get_block_by_id(block_id=block_id, actor=user)
1712
+ block = self.block_manager.get_block_by_id(block_id=block_id, actor=self.user_manager.get_user_by_id(user_id=user_id))
1888
1713
  if block is None:
1889
1714
  raise ValueError(f"Block with id {block_id} not found")
1715
+ self.blocks_agents_manager.add_block_to_agent(agent_id, block_id, block_label=block.label)
1890
1716
 
1891
- # Link a block to an agent's memory
1892
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1893
- letta_agent.memory.link_block(block=block)
1894
- assert block.label in letta_agent.memory.list_block_labels()
1895
-
1896
- # Recompile the agent memory
1897
- letta_agent.rebuild_memory(force=True, ms=self.ms)
1898
-
1899
- # save agent
1900
- save_agent(letta_agent, self.ms)
1901
-
1902
- updated_agent = self.ms.get_agent(agent_id=agent_id)
1903
- if updated_agent is None:
1904
- raise ValueError(f"Agent with id {agent_id} not found after linking block")
1905
- assert block.label in updated_agent.memory.list_block_labels()
1906
-
1907
- return updated_agent.memory
1717
+ # get agent memory
1718
+ memory = self.load_agent(agent_id=agent_id).agent_state.memory
1719
+ return memory
1908
1720
 
1909
1721
  def unlink_block_from_agent_memory(self, user_id: str, agent_id: str, block_label: str, delete_if_no_ref: bool = True) -> Memory:
1910
1722
  """Unlink a block from an agent's memory. If the block is not linked to any agent, delete it."""
1723
+ self.blocks_agents_manager.remove_block_with_label_from_agent(agent_id=agent_id, block_label=block_label)
1911
1724
 
1912
- # Get the user
1913
- user = self.user_manager.get_user_by_id(user_id=user_id)
1914
-
1915
- # Link a block to an agent's memory
1916
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1917
- unlinked_block = letta_agent.memory.unlink_block(block_label=block_label)
1918
- assert unlinked_block.label not in letta_agent.memory.list_block_labels()
1919
-
1920
- # Check if the block is linked to any other agent
1921
- # TODO needs reference counting GC to handle loose blocks
1922
- # block = self.block_manager.get_block_by_id(block_id=unlinked_block.id, actor=user)
1923
- # if block is None:
1924
- # raise ValueError(f"Block with id {block_id} not found")
1925
-
1926
- # Recompile the agent memory
1927
- letta_agent.rebuild_memory(force=True, ms=self.ms)
1928
-
1929
- # save agent
1930
- save_agent(letta_agent, self.ms)
1931
-
1932
- updated_agent = self.ms.get_agent(agent_id=agent_id)
1933
- if updated_agent is None:
1934
- raise ValueError(f"Agent with id {agent_id} not found after linking block")
1935
- assert unlinked_block.label not in updated_agent.memory.list_block_labels()
1936
- return updated_agent.memory
1725
+ # get agent memory
1726
+ memory = self.load_agent(agent_id=agent_id).agent_state.memory
1727
+ return memory
1937
1728
 
1938
1729
  def update_agent_memory_limit(self, user_id: str, agent_id: str, block_label: str, limit: int) -> Memory:
1939
1730
  """Update the limit of a block in an agent's memory"""
1731
+ block = self.get_agent_block_by_label(user_id=user_id, agent_id=agent_id, label=block_label)
1732
+ self.block_manager.update_block(
1733
+ block_id=block.id, block_update=BlockUpdate(limit=limit), actor=self.user_manager.get_user_by_id(user_id=user_id)
1734
+ )
1735
+ # get agent memory
1736
+ memory = self.load_agent(agent_id=agent_id).agent_state.memory
1737
+ return memory
1738
+
1739
+ def upate_block(self, user_id: str, block_id: str, block_update: BlockUpdate) -> Block:
1740
+ """Update a block"""
1741
+ return self.block_manager.update_block(
1742
+ block_id=block_id, block_update=block_update, actor=self.user_manager.get_user_by_id(user_id=user_id)
1743
+ )
1940
1744
 
1941
- # Get the user
1942
- user = self.user_manager.get_user_by_id(user_id=user_id)
1943
-
1944
- # Link a block to an agent's memory
1945
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1946
- letta_agent.memory.update_block_limit(label=block_label, limit=limit)
1947
- assert block_label in letta_agent.memory.list_block_labels()
1948
-
1949
- # write out the update the database
1950
- self.block_manager.create_or_update_block(block=letta_agent.memory.get_block(block_label), actor=user)
1951
-
1952
- # check that the block was updated
1953
- updated_block = self.block_manager.get_block_by_id(block_id=letta_agent.memory.get_block(block_label).id, actor=user)
1954
- assert updated_block and updated_block.limit == limit
1955
-
1956
- # Recompile the agent memory
1957
- letta_agent.rebuild_memory(force=True, ms=self.ms)
1958
-
1959
- # save agent
1960
- save_agent(letta_agent, self.ms)
1961
-
1962
- updated_agent = self.ms.get_agent(agent_id=agent_id)
1963
- if updated_agent is None:
1964
- raise ValueError(f"Agent with id {agent_id} not found after linking block")
1965
- assert updated_agent.memory.get_block(label=block_label).limit == limit
1966
- return updated_agent.memory
1745
+ def get_agent_block_by_label(self, user_id: str, agent_id: str, label: str) -> Block:
1746
+ """Get a block by label"""
1747
+ # TODO: implement at ORM?
1748
+ for block_id in self.blocks_agents_manager.list_block_ids_for_agent(agent_id=agent_id):
1749
+ block = self.block_manager.get_block_by_id(block_id=block_id, actor=self.user_manager.get_user_by_id(user_id=user_id))
1750
+ if block.label == label:
1751
+ return block
1752
+ return None