Building Stateful Agents with Letta

Letta agents can automatically manage long-term memory, load data from external sources, and call custom tools. Unlike in other frameworks, Letta agents are stateful, so they keep track of historical interactions and reserve part of their context to read and write memories which evolve over time.

Key features:

  • Python/TypeScript SDKs & REST API
  • Persistence
  • Tool calling (support for custom tools & Composio tools)
  • Memory management
  • Deployment
  • Streaming support

Letta manages a reasoning loop for agents. At each agent step (i.e. iteration of the loop), the state of the agent is checkpointed and persisted to the database.

You can interact with agents from a REST API, the ADE, and TypeScript / Python SDKs. As long as they are connected to the same service, all of these interfaces can be used to interact with the same agents.

Agents vs Threads

In Letta, you can think of an agent as a single entity that has a single message history which is treated as infinite. The sequence of interactions the agent has experienced through its existence make up the agent’s state (or memory).

One distinction between Letta and other agent frameworks is that Letta does not have the notion of message threads (or sessions). Instead, there are only stateful agents, which have a single perpetual thread (sequence of messages).

The reason we use the term agent rather than thread is because Letta is based on the principle that all agents interactions should be part of the persistent memory, as opposed to building agent applications around ephemeral, short-lived interactions (like a thread or session).

If you would like to create common starting points for new conversation “threads”, we recommending using agent templates to create new agents for each conversation, or directly copying agent state from an existing agent.

For multi-users applications, we recommend creating an agent per-user, though you can also have multiple users message a single agent (but it will be a single shared message history).

Create an agent

Python
1from letta import Letta
2
3client = Letta(token="LETTA_API_KEY")
4
5agent_state = client.agents.create(
6 name="my_agent",
7 memory_blocks=[
8 {"label": "human", "limit": 2000, "value": "Name: Bob"},
9 {"label": "persona", "limit": 2000, "value": "You are a friendly agent"}
10 ],
11 model="openai/gpt-4o-mini",
12 embedding="openai/text-embedding-ada-002"
13)

Once an agent is created, you can message it:

Python
1# Message an agent
2response = client.agents.messages.create(
3 agent_id=agent_state.id,
4 messages=[
5 {"role": "user", "content": "hello"}
6 ]
7)
8print("Usage", response.usage)
9print("Agent messages", response.messages)

Message Types

The response object contains the following attributes:

  • usage: The usage of the agent after the message was sent (the prompt tokens, completition tokens, and total tokens)
  • message: A list of LettaMessage objects, generated by the agent

LettaMessage

The LettaMessage object is a simplified version of the Message object stored in the database backend. Since a Message can include multiple events like a chain-of-thought and function calls, LettaMessage simplifies messages to have the following types:

  • reasoning_message: The inner monologue (chain-of-thought) of the agent
  • tool_call_message: An agent’s tool (function) call
  • tool_call_return: The result of executing an agent’s tool (function) call
  • assistant_message: An agent calling the send_message tool to communicate with the user
  • system_message: A system message (for example, an alert about the user logging in)
  • user_message: A user message

The assistant_message message type is a convenience wrapper around the tool_call_message when the tool call is the predefined send_message tool that makes it easier to parse agent messages. If you prefer to see the raw tool call even in the send_message case, you can set use_assistant_message to false in the request config (see the endpoint documentation).

Retrieving an agent’s state

The agent’s state is always persisted, so you can retrieve an agent’s state by either its ID or name.

Python
1# get the agent by ID
2agent_state = client.agents.get(agent_id="agent-42c61916-1fb3-4195-85l7-b865f7e452dd")

List agents

Python
1# list `AgentState` objects of all agents
2agents = client.agents.list()

Delete an agent

Python
1# delete an agent
2client.agents.delete(agent_id="agent-42c61916-1fb3-4195-85l7-b865f7e452dd")
Built with