Message Types
When you interact with a Letta agent and retrieve its message history using client.agents.messages.list()
, you’ll receive various types of messages that represent different aspects of the agent’s execution. This guide explains all message types and how to work with them.
Overview
Letta uses a structured message system where each message has a specific message_type
field that indicates its purpose. Messages are returned as instances of LettaMessageUnion
, which is a discriminated union of all possible message types.
Message Type Categories
User and System Messages
user_message
Messages sent by the user or system events packaged as user input.
Structure:
Special User Message Subtypes:
User messages can contain JSON with a type
field indicating special message subtypes:
-
heartbeat
- Automated timer events that allow agents to chain multiple tool calls. See Heartbeats for more details. -
login
- User login events -
user_message
- Standard user messages -
system_alert
- System notifications and alerts
system_message
Messages generated by the system, typically used for internal context.
Structure:
Note: System messages are never streamed back in responses; they’re only visible when paginating through message history.
Agent Reasoning and Responses
reasoning_message
Represents the agent’s internal reasoning or “chain of thought.”
Structure:
Fields:
reasoning
- The agent’s internal thought processsource
- Whether this was generated by a model with native reasoning (like o1) or via promptingsignature
- Optional cryptographic signature for reasoning verification (for models that support it)
hidden_reasoning_message
Represents reasoning that has been hidden from the response.
Structure:
Fields:
state: "redacted"
- The provider redacted the reasoning contentstate: "omitted"
- The API chose not to include reasoning (e.g., for o1/o3 models)
assistant_message
The actual message content sent by the agent (typically via the send_message
tool).
Structure:
Tool Execution Messages
tool_call_message
A request from the agent to execute a tool.
Structure:
Example:
tool_return_message
The result of a tool execution.
Structure:
Fields:
tool_return
- The formatted return value from the toolstatus
- Whether the tool executed successfullystdout
/stderr
- Captured output from the tool execution (useful for debugging)
Human-in-the-Loop Messages
approval_request_message
A request for human approval before executing a tool.
Structure:
See Human-in-the-Loop for more information on this experimental feature.
approval_response_message
The user’s response to an approval request.
Structure:
Working with Messages
Listing Messages
Filtering Messages by Type
Filtering Out Special User Messages
When working with user messages, you may want to filter out internal system messages like heartbeats:
Pagination
Messages support cursor-based pagination:
Message Metadata Fields
All message types include these common fields:
id
- Unique identifier for the messagedate
- ISO 8601 timestamp of when the message was createdmessage_type
- The discriminator field identifying the message typename
- Optional name field (varies by message type)otid
- Offline threading ID for message correlationsender_id
- The ID of the sender (identity or agent ID)step_id
- The step ID associated with this messageis_err
- Whether this message is part of an error step (debugging only)seq_id
- Sequence ID for orderingrun_id
- The run ID associated with this message
Best Practices
1. Use Type Discriminators
Always check the message_type
field to safely access type-specific fields:
2. Handle Special User Messages
When displaying conversations to end users, filter out internal messages:
3. Track Tool Execution
Match tool calls with their returns using tool_call_id
:
See Also
- Heartbeats - Understanding heartbeat messages and tool chaining
- Human-in-the-Loop - Using approval messages
- Streaming Responses - Receiving messages in real-time
- API Reference - Full API documentation