1. Executive Summary
At this commit the letta-ai/letta main branch
holds no source. On 15 August 2026 87fd37aa…
(#3430) removed the V1 API server — about 367,000 lines — leaving the
README, policies and a citation file. The README points to letta-ai/letta-code
as the current source, covering the agent harness, terminal UI, App
Server and channels, and keeps the retired server on the archive
branch, two non-source commits after the previous pin. What follows is
the V1 server as it stands there, and every path below refers to that
tree. letta-code has no report here.
letta is an agent runtime with memory as part of agent
state. Its memory model is older and deeper than most repos here: core
in-context memory blocks, archival semantic memory, recall over
conversation history, file/source memory, and newer git-backed memory
projection support.
The essential distinction:
- Core memory is prompt-resident block state that the agent edits with tools.
- Archival memory is searchable passage storage.
- Recall memory is prior conversation history search.
- Source/file memory is document passages attached through sources/files.
Letta's strongest idea is making memory editing explicit inside the tool loop, with block-level prompt recompilation after changes. Its biggest risk is that the agent is trusted to edit its own core memory; correctness depends on tool rules, exact-string edits, read-only flags, and runtime enforcement.
2. Mental Model
Letta memory layers:
Memory: in-context memory object containingBlockobjects.Block: labeled prompt memory section such ashuman,persona,system/persona, or custom paths.ArchivalPassage: long-term semantic memory attached to an archive/agent.SourcePassage: chunks from uploaded files/sources.- Conversation messages: searchable prior interaction history.
Lifecycle:
Diagram source
%% caption: a memory write mutates agent state and the system prompt is rebuilt from it, so the edit takes effect by recompiling the prompt rather than by injection
flowchart TB
P["agent receives prompt"] --> SP["system prompt includes<br/>compiled core memory"]
SP --> T["agent calls memory tools"]
T --> EX["core tool executor:<br/>mutate AgentState.memory,<br/>or insert / search passages"]
EX --> AM["AgentManager persists the change<br/>and rebuilds the system prompt"]
AM --> SP
style SP fill:#e7efe9,stroke:#3d6b59The loop is the design. Core memory is not retrieved — it is compiled into the system prompt, so a write changes the prompt itself and the next call sees the new text without any search step.
Letta is memory-as-agent-state, not just memory-as-RAG.
3. Architecture
Core files:
letta/letta/schemas/memory.py:Memory,BasicBlockMemory, rendering/compilation.letta/letta/functions/function_sets/base.py: tool schemas/docs for memory functions.letta/letta/services/tool_executor/core_tool_executor.py: actual execution of core memory tools.letta/letta/services/block_manager.py: block CRUD, tags, prompt rebuild triggers.letta/letta/services/passage_manager.py: archival/source passage persistence.letta/letta/services/message_manager.py: conversation search extraction/indexing.letta/letta/orm/block.py: block schema and optimistic locking.letta/letta/orm/passage.py: archival/source passage schema.letta/letta/services/memory_repo/*: git-backed memory projection.
Runtime shape:
Diagram source
%% caption: the managers behind the core memory tools, with blocks compiled into the prompt and passages held for archival search
flowchart TD
Agent["Agent<br/>loop"] --> Tools["Core memory<br/>tools"]
Tools --> Executor["LettaCoreToolExecutor"]
Executor --> AgentState["AgentState.memory"]
Executor --> BlockMgr["BlockManager"]
Executor --> PassageMgr["PassageManager"]
Executor --> MsgMgr["MessageManager"]
BlockMgr --> DB["DB blocks"]
PassageMgr --> Passages["Archival/source passages +<br/>embeddings"]
MsgMgr --> Messages["Conversation<br/>messages"]
BlockMgr --> Prompt["Rebuilt system<br/>prompt"]4. Essential Implementation Paths
Core prompt rendering:
Memory.compile()inletta/letta/schemas/memory.py.- Renders XML-ish
<memory_blocks>for normal agents. - For git-enabled agents,
_render_memory_blocks_git()renderssystem/personaas<self>and othersystem/*blocks under<memory>. - For selected Anthropic agent types,
_render_memory_blocks_line_numbered()adds line numbers as view-only context.
Core memory mutation:
- Tool definitions are documented in
letta/letta/functions/function_sets/base.py. - Actual runtime implementation is
LettaCoreToolExecutor.execute()inletta/letta/services/tool_executor/core_tool_executor.py. core_memory_append(),core_memory_replace(),memory_replace(),memory_insert(), andmemory_apply_patch()mutateagent_state.memory.- After mutation, executor calls
agent_manager.update_memory_if_changed_async(...).
Archival write/search:
archival_memory_insert()incore_tool_executor.pycallspassage_manager.insert_passage(...)then rebuilds the system prompt.archival_memory_search()delegates toagent_manager.search_agent_archival_memory_async(...).PassageManager.create_agent_passage_async()andcreate_agent_passages_async()persistArchivalPassagerows with embeddings and tags.
Conversation recall:
conversation_search()incore_tool_executor.py.- Calls
message_manager.search_messages_async(...). - Filters out tool messages and assistant calls to
conversation_searchto avoid recursive results. - Formats structured results with timestamp, time delta, role, content, and RRF/vector/FTS metadata when available.
Block persistence:
BlockManager.update_block_async()persists block changes, handles tag rows, and rebuilds system prompts for connected agents when prompt-affecting fields change.letta/letta/orm/block.pydefinesversion_id_coloptimistic locking.
Tests:
letta/tests/test_memory.pyletta/tests/managers/test_block_manager.pyletta/tests/managers/test_passage_manager.pyletta/tests/managers/test_message_manager.pyletta/tests/test_block_manager_noop_update.pyletta/tests/performance_tests/test_insert_archival_memory.py
5. Memory Data Model
Core memory:
Memory.blocks: list ofBlockschemas.Block: label, value, description, limit, read-only flag, metadata, tags.BlockORM includes optimisticversion, current history pointer, template/project/org fields.
Archival/source memory:
BasePassage:id,text, embedding config, metadata, tags, embedding.ArchivalPassage: attached to archive.SourcePassage: attached to source/file and file name.- Passage tags are stored both in JSON and a junction table for filtering.
Conversation recall:
- Message records are indexed/searchable by
MessageManager. _extract_message_text()normalizes complex message content into JSON text and skips heartbeat/send-message/search tool noise.
Scoping:
- Organization/user/agent boundaries are embedded throughout managers and ORM mixins.
- Agents connect to blocks through
blocks_agents. - Archives connect to agents through archive manager/pivot tables.
6. Retrieval Mechanics
Retrieval layers:
- Core memory is always prompt-resident.
- Archival search uses semantic passage retrieval through the agent manager/passage infrastructure.
- Conversation search uses hybrid search metadata:
combined_score,vector_rank,fts_rank,search_mode. - Source/file passages support document memory.
Letta's main retrieval idea is not "always search everything"; it gives the agent tools:
- Use
archival_memory_searchwhen it needs long-term facts. - Use
conversation_searchwhen it needs prior conversation details. - Rely on core memory for always-on stable state.
Risk: the agent must know which layer to query. Core memory can become stale or bloated if the agent edits poorly.
7. Write Mechanics
Core memory writes are precise edits:
- Append.
- Exact string replace.
- Insert at line.
- Apply unified-diff-like patch.
- Create/delete/rename memory blocks through the broader
memorytool path.
Guardrails:
- Read-only block check.
- Line-number-prefix rejection.
- Exact replace must match once.
- Patch hunk must match unique context.
- Prompt is rebuilt after memory changes.
Archival writes:
PassageManagerpads embeddings for Postgres vector dimensions.- Removes null bytes before DB insert.
- Deduplicates tags.
- Batch creation available.
8. Agent Integration
Memory is deeply integrated with Letta's agent loop.
Core tools:
core_memory_appendcore_memory_replacememory_replacememory_insertmemory_apply_patchmemoryarchival_memory_insertarchival_memory_searchconversation_search
Constants in letta/letta/constants.py define base memory
tool sets for different agent families, including v1/v2/v3 and sleeptime
variants.
This is powerful because memory edits happen as part of the agent's reasoning/action loop. It is risky because the same model that may be confused is allowed to edit its own durable state.
9. Reliability, Safety, and Trust
Strengths:
- Core memory block limits and read-only flags.
- Optimistic locking on block ORM.
- Tool executor catches errors and returns tool errors rather than crashing the loop.
- Prompt rebuild happens after persisted block changes.
- Conversation search suppresses recursive/tool-result pollution.
- Exact edit tools reduce accidental broad rewrites.
Risks:
- Agent-controlled memory can silently encode wrong beliefs.
- No first-class verified/candidate/rejected trust layer.
- Core memory is prompt-resident and token-sensitive.
- Exact-string editing is brittle.
- Archival memory insertion trusts agent-provided content.
- Git-backed memory projection adds another synchronization surface.
Two mechanisms that look like marks and are not.
BlockHistory (letta/orm/block_history.py)
stores block snapshots with an actor type and id and a per-block
sequence number, but it is an undo/redo stack: a checkpoint taken after
an undo deletes the rows ahead of the current position
(block_manager.py:877-880), and the rows cascade away with
the block, so it is not an append-only mutation record and
audit_log is withheld.
RequiresApprovalToolRule
(letta/schemas/tool_rule.py:348) can hold any tool, memory
tools included, for a person's approval, but it is a per-agent tool rule
with no memory-specific surface, so human_review is
withheld.
The organization scope is real and sits in one place:
apply_access_predicate in the ORM base adds
organization_id = actor.organization_id to every read that
is given an actor. A list called without one logs "SECURITY: Listing
org-scoped model … without actor. This bypasses organization
filtering" and proceeds, so the boundary holds because the managers
pass the actor, not because the base refuses.
10. Tests, Evals, and Benchmarks
Relevant tests exist for memory, block, passage, message, summarizer,
and archival-memory performance. I did not run them.
tests/managers/test_block_manager.py::test_get_blocks_comprehensive
is the exclusion case: two organizations' blocks, each actor seeing
exactly its own, and each actor's lookup of the other's labels asserted
empty.
Missing tests I would want:
- Agent memory edit mistakes under line-numbered prompts.
- Prompt injection through archival search results.
- Read-only block enforcement across every memory tool path.
- Conflict handling when user facts change.
- Core memory token pressure and summarization interactions.
11. For Your Own Build
Steal
- Separate always-on core memory from searchable archival memory.
- Use labeled blocks with descriptions, limits, and read-only flags.
- Rebuild prompts automatically after memory mutation.
- Exact edit tools with uniqueness checks.
- Conversation search filters that avoid recursive tool-output pollution.
- Git-backed memory projection for developers comfortable with file semantics.
Avoid
- Letting the agent directly rewrite its own identity/user model without verification.
- Core memory as unstructured text blocks can become a junk drawer.
- Multiple memory tool generations create conceptual complexity.
- Exact string replacement is safe but brittle for long evolving blocks.
Fit
Borrow:
- Core vs archival vs recall separation.
- Block metadata and read-only controls.
- Prompt rebuild after state changes.
- Patch-style memory editing.
Avoid copying:
- Unlimited model authority over durable beliefs.
- Unstructured core memory without provenance.
- Too many overlapping memory tool APIs unless backward compatibility requires it.
Letta is worth studying if you are building a full agent runtime. It is heavier than needed for a standalone memory service.
12. Open Questions
- How does Letta decide when to compact/summarize versus preserve raw conversation history?
- How are wrong core memories corrected in practice?
- How mature is git-backed memory relative to block-backed memory?
- What retrieval fusion does
search_messages_asyncuse internally across DB backends?
Appendix: File Index
Paths are at the archive
branch; the main branch no longer has them.
- Core memory schema/rendering:
letta/letta/schemas/memory.py. - Tool docs/stubs:
letta/letta/functions/function_sets/base.py. - Tool runtime:
letta/letta/services/tool_executor/core_tool_executor.py. - Blocks:
letta/letta/services/block_manager.py,letta/letta/orm/block.py. - Passages:
letta/letta/services/passage_manager.py,letta/letta/orm/passage.py. - Conversation search:
letta/letta/services/message_manager.py. - Git memory:
letta/letta/services/memory_repo/. - Tests:
letta/tests/, with the cross-organization case intests/managers/test_block_manager.py. - Scope predicate:
letta/orm/sqlalchemy_base.py(apply_access_predicate). - Block history:
letta/orm/block_history.py,letta/services/block_manager.py(checkpoint_block_async).
History
2026-09-15 — 5bcdd177…
— 5 commits on, 2026-09-10. Screened before reading: the main branch is
Markdown and a citation file with no manifests, and its
AGENTS.md addresses a reading agent, read as data; nothing
was installed or run. The subject of this report left the branch: 87fd37aa…
(#3430, 2026-08-15) archived the V1 server, and the README names
letta-ai/letta-code as the current source. The
archive branch at 56ba9c25…
differs from the previous pin only in an issue-guard workflow and
AGENTS.md, so the body stands for that tree and is framed
as archived. Read against it for evidence records:
scope_enforced kept on the ORM organization predicate, with
the actor-optional list noted; negative_eval added on the
cross-organization block test, present at both earlier readings;
audit_log withheld on BlockHistory, an undo
stack that deletes forward rows; human_review withheld on a
generic tool-approval rule. Two marks.
2026-08-06 — ff19ffea…
— 3 commits on, none of them source. The diff is .github/
issue templates and a workflow, .gitignore,
AGENTS.md, AI_POLICY.md and the README;
git diff --name-only filtered to *.py is
empty. The memory mechanism is unchanged and no published claim is
stale. Screened again: 0 auto-run surfaces, 4 build-time exec paths, and
an AGENTS.md addressed to a reading agent, read as data.
Nothing was installed or run.
2026-07-26 — 6d8cb7fd…
— first reading.