Skip to content

<- Back to Overview

How YouLab integrates with the Letta agent framework.

Letta provides stateful AI agents with persistent memory. YouLab leverages:

  • Persistent state - Agent state survives restarts
  • Two-tier memory - Core (in-context) + Archival (vector search)
  • Streaming - Real-time response delivery
  • Tool system - Custom tools for memory editing

For complete Letta documentation, see docs.letta.com.


from letta_client import Letta
# Self-hosted (YouLab default)
client = Letta(base_url="http://localhost:8283")
# From settings
from youlab_server.config import get_settings
settings = get_settings()
client = Letta(base_url=str(settings.letta_base_url))

YouLab creates agents with course-specific configuration:

agent = client.agents.create(
name="youlab_user123_college-essay",
memory_blocks=[
{"label": "persona", "value": persona_string},
{"label": "human", "value": human_string},
],
model="anthropic/claude-sonnet-4-20250514",
embedding="openai/text-embedding-3-small",
metadata={
"youlab_user_id": "user123",
"youlab_course_id": "college-essay",
},
)

Always visible in agent context:

blocks = client.agents.core_memory.list(agent_id)
human_block = next(b for b in blocks if b.label == "human")
# Update block
client.agents.core_memory.update(
agent_id=agent_id,
block_id=human_block.id,
value=new_value,
)

Long-term storage with semantic search:

# Insert
client.agents.archival_memory.insert(
agent_id=agent_id,
text="[ARCHIVED 2025-01-01]\nContext...",
)
# Search
results = client.agents.archival_memory.search(
agent_id=agent_id,
query="essay topics",
limit=5,
)

with client.agents.messages.stream(
agent_id=agent.id,
input="Hello!",
stream_tokens=False,
include_pings=True,
) as stream:
for chunk in stream:
if chunk.message_type == "assistant_message":
print(chunk.content)
TypePurpose
reasoning_messageAgent thinking
tool_call_messageTool invocation
assistant_messageResponse to user
stop_reasonStream complete
usage_statisticsToken counts

def my_tool(arg: str) -> str:
"""
Tool description.
Args:
arg: Input argument
Returns:
Result string
"""
return f"Processed: {arg}"
tool = client.tools.upsert_from_function(func=my_tool)
client.agents.tools.attach(agent_id=agent.id, tool_id=tool.id)

# Good
client = Letta()
for user in users:
agent = client.agents.retrieve(...)
# Bad - creates overhead
for user in users:
client = Letta() # Don't do this
_cache: dict[str, str] = {}
async def get_agent_id(user_id: str) -> str:
if user_id not in _cache:
agent = await find_agent(user_id)
_cache[user_id] = agent.id
return _cache[user_id]
# Good - parseable
"[USER] Alice | Student\n[TASK] Essay brainstorming"
# Bad - unstructured
"Alice is a student working on essay brainstorming"