Skip to content

Letta

Letta (formerly MemGPT) is a platform for building stateful AI agents with persistent memory. memio provides three adapters for Letta.

Installation

pip install memio[letta]

This installs the letta-client package as a dependency.

Setup

Letta supports two deployment modes:

Mode Setup
Letta Cloud Sign up at app.letta.com, get an API key at app.letta.com/api-keys.
Self-hosted Run docker run -p 8283:8283 -e OPENAI_API_KEY=... letta/letta:latest. No Letta API key needed.

All adapters require an agent_id. Create an agent via the SDK:

from letta_client import Letta

# Cloud
client = Letta(api_key="your-letta-api-key")
# Self-hosted
# client = Letta(base_url="http://localhost:8283")

agent = client.agents.create(
    model="openai/gpt-4.1",
    memory_blocks=[{"label": "human", "value": "Name: user"}],
)
print(agent.id)  # use this as agent_id

Supported stores

Store Adapter class
FactStore LettaFactAdapter
HistoryStore LettaHistoryAdapter
DocumentStore LettaDocumentAdapter

GraphStore is not supported — Letta has no knowledge graph.

FactStore -- LettaFactAdapter

Maps memio facts to Letta archival passages (agent-scoped).

from memio import Memio
from memio.providers.letta import LettaFactAdapter

client = Memio(
    facts=LettaFactAdapter(api_key="letta-xxx", agent_id="agent-123"),
)

fact = await client.facts.add(content="likes coffee")
results = await client.facts.search(query="coffee")
await client.facts.delete(fact_id=fact.id)

Scoping limitation

Letta passages are agent-scoped at the adapter level. Passing user_id or agent_id to search(), get_all(), or delete_all() raises NotSupportedError. Omit both arguments to operate on all passages for the configured agent:

# OK — no scoping arguments
results = await client.facts.search(query="coffee")
all_facts = await client.facts.get_all()

# raises NotSupportedError
results = await client.facts.search(query="coffee", user_id="alice")

Update behavior

Letta's passages API has no native update. The adapter emulates update via delete + create. This means the passage ID changes after an update:

updated = await client.facts.update(fact_id=fact.id, content="likes tea")
assert updated.id != fact.id  # new ID assigned

HistoryStore -- LettaHistoryAdapter

Maps memio sessions to Letta conversations. Sending messages triggers the agent to process and respond.

from memio import Memio, Message
from memio.providers.letta import LettaHistoryAdapter

client = Memio(
    history=LettaHistoryAdapter(api_key="letta-xxx", agent_id="agent-123"),
)

await client.history.add(
    session_id="session-1",
    user_id="alice",
    messages=[Message(role="user", content="hello")],
)
messages = await client.history.get(session_id="session-1")

The adapter filters messages to only return user and assistant messages, excluding internal types (reasoning, tool calls, system messages).

DocumentStore -- LettaDocumentAdapter

Maps memio documents to Letta archival passages, the same underlying API as FactStore but oriented toward longer content.

from memio import Memio
from memio.providers.letta import LettaDocumentAdapter

client = Memio(
    documents=LettaDocumentAdapter(api_key="letta-xxx", agent_id="agent-123"),
)

doc = await client.documents.add(content="deployment guide for production")
results = await client.documents.search(query="deployment")
await client.documents.delete(doc_id=doc.id)

Update behavior is the same as FactStore (delete + create, ID changes).

Self-hosted example

from memio import Memio
from memio.providers.letta import LettaFactAdapter, LettaHistoryAdapter, LettaDocumentAdapter

kwargs = {"base_url": "http://localhost:8283", "agent_id": "agent-123"}

client = Memio(
    facts=LettaFactAdapter(**kwargs),
    history=LettaHistoryAdapter(**kwargs),
    documents=LettaDocumentAdapter(**kwargs),
)