# Memory Blocks and the Memory Filesystem


The Letta agent harness, Letta Code, keeps memory alive for agents that can run for a long time by splitting memory into two layers. The first layer stays tiny and always in context: `persona` and `human` carry identity and relationship context. The second layer lives in MemFS tracked by git and holds the durable material that accumulates over time. The older v1 pages for [Memory Blocks](https://docs.letta.com/guides/core-concepts/memory/memory-blocks) and [Stateful Agents](https://docs.letta.com/guides/core-concepts/stateful-agents) are historical lineage only; they do not describe the current v2 harness. For feature-level usage, see the [official memory docs](https://docs.letta.com/letta-agent/memory) and the [official MemFS docs](https://docs.letta.com/letta-agent/memfs).

## Layer 1: memory blocks

`src/agent/memory.ts` defines the default block labels `persona` and `human`, and it loads them from `src/agent/prompts/persona.mdx` and `src/agent/prompts/human.mdx`. Those MDX files seed the prompt with who the agent is and what it knows about the person it is working with.

These blocks do one job: they keep identity and relationship context near the model on every turn. They do not try to act as a general knowledge store, and the harness treats them as prompt seeding rather than as a user-facing editing surface. On MemFS-enabled agents the block content itself lives in the filesystem as `system/` files and stays in context through the per-compile projection.

## Layer 2: MemFS

`src/agent/memory-filesystem.ts` scopes each agent's memory to `~/.letta/agents/<agentId>/memory` and creates the `system/` directory that the prompt compiler reads first. That filesystem holds the memory blocks themselves, plus notes and agent-scoped skills, and it becomes the durable memory tree for the agent.

Git tracking gives the durable layer auditability, rollback, portability, and an optional sync path to a remote the user owns through `/memory-repository`. The git path in `src/agent/memory-git.ts`, `src/agent/memory-git-hooks.ts`, and `src/agent/memory-git-signing.ts` installs pre-commit and post-commit hooks and turns off commit signing for harness-managed identities. The commit log records what the agent believed and when, making the git history the audit trail for memory state.

## How memory changes

During a turn, `src/tools/impl/memory.ts` and `src/tools/impl/memory-apply-patch.ts` edit memory files and the harness records the result as a commit. The change stays inside the agent's memory repo, so the model sees the update on the next read without any separate manual sync step.

Outside the turn, reflection and dreaming can also rewrite memory files after a run; see [dreaming and reflection](./04-dreaming-and-reflection.md). In v1, server-side functions wrote memory edits into a database; in v2, memory edits are file edits with git history.

## How memory is read

`src/backend/local/system-prompt-compilation.ts` renders MemFS with `renderMemfsProjection` and injects the result with `injectCoreMemory`. That path turns the committed filesystem into prompt text before the model sees it.

`src/websocket/listener/turn-setup.ts` rebuilds the current world before each run, while `src/websocket/listener/memfs-sync.ts` pulls MemFS lazily when a listener first sees an agent. Operator surfaces help with inspection, but they stay off the critical path: the `/memory` and `/palace` viewers live in `src/cli/components/MemoryTabViewer.tsx`, `src/cli/components/MemfsTreeViewer.tsx`, and `src/web/generate-memory-viewer.ts`, and `/doctor` comes from `src/skills/builtin/context-doctor/SKILL.md`.

## What is shared vs scoped

Agent memory belongs to the agent, not to a single conversation. Every conversation that runs under the same agent reads the same memory, while the conversation queues remain separate. That split lines up with the turn lifecycle in [Anatomy of a turn](./01-anatomy-of-a-turn.md), the queue model in [Conversations, queues, and interrupts](./02-conversations-queues-and-interrupts.md), and the app server boundary in [The app server and the SDK](./08-the-app-server-and-the-sdk.md).

```mermaid
flowchart LR
  subgraph Prompt["Layer 1: always in context"]
    persona["persona"]
    human["human"]
  end

  subgraph MemFS["Layer 2: MemFS"]
    root["~/.letta/agents/&lt;agentId&gt;/memory/"]
    system["system/"]
    other["other memory files"]
    hooks["pre/post-commit hooks installed;<br/>commit signing disabled"]
  end

  tools["memory / memory_apply_patch tools"]
  reflection["reflection / dreaming"]
  commit["harness commit"]
  remote["optional user-owned remote<br/>via /memory-repository"]
  prompt["system prompt assembly<br/>renderMemfsProjection + injectCoreMemory"]

  tools --> root
  reflection --> root
  root --> hooks --> commit --> remote
  root --> prompt
  system --> prompt
  other --> prompt
  persona --> prompt
  human --> prompt
```

## Where to look in the code

- `src/agent/memory.ts` and `src/agent/prompts/{persona,human}.mdx` define the two seeded memory blocks.
- `src/agent/memory-filesystem.ts` sets the memory root for each agent and creates `system/`.
- `src/agent/memory-git.ts`, `src/agent/memory-git-hooks.ts`, and `src/agent/memory-git-signing.ts` handle clone, pull, commit, hooks, and commit signing.
- `src/tools/impl/memory.ts` and `src/tools/impl/memory-apply-patch.ts` write memory during turns.
- `src/backend/local/system-prompt-compilation.ts`, `src/websocket/listener/turn-setup.ts`, and `src/websocket/listener/memfs-sync.ts` read and hydrate memory at runtime.
- `src/cli/components/MemoryTabViewer.tsx`, `src/cli/components/MemfsTreeViewer.tsx`, `src/cli/commands/memory-repository.ts`, and `src/skills/builtin/context-doctor/SKILL.md` cover operator inspection, remote sync, and audits.

