A persistent memory system for Claude Code that enables Claude to remember important information across sessions. Built as a Claude Code plugin with container-based deployment.
- Automatic Context Loading - Top memories are loaded at session start
- On-Demand Storage - Store important learnings with
/ltm:remember - Semantic Search - Find memories by keyword with
/ltm:recall - Smart Eviction - Graceful degradation through phases (Full → Hint → Abstract → Removed)
- Difficulty Tracking - Memories from challenging tasks are prioritized
- Git-Friendly - Human-readable markdown files with YAML frontmatter
- Integrity Tools - Check and fix system health with
/ltm:checkand/ltm:fix - Extended Thinking Integration - Automatically consults memory in "think harder" and "ultrathink" modes
- Claude Code CLI installed
- Podman or Docker
Install LTM as a Claude Code plugin directly from GitHub:
# Add the LTM marketplace
claude plugin marketplace add https://github.com/JoshSalomon/claude-ltm.git
# Install the plugin
claude plugin install ltm@claude-ltm
# Enable extended thinking integration (run inside Claude Code)
# This adds memory consultation instructions to your project's CLAUDE.md
/ltm:initThe plugin automatically:
- Runs an ephemeral container for each Claude Code session
- Registers the MCP server for memory tools
- Provides slash commands (
/ltm:remember,/ltm:recall,/ltm:forget,/ltm:status, etc.)
The /ltm:init command enables:
- Proactive memory search before debugging and implementing features
- Extended thinking consultation that automatically searches memories during complex reasoning
claude plugin update ltm@claude-ltmclaude plugin uninstall ltm@claude-ltm
claude plugin marketplace remove claude-ltmMemory data is stored in your project's .claude/ltm/ directory:
your-project/
└── .claude/
└── ltm/
├── index.json # Memory index - git-tracked
├── stats.json # Access statistics - git-ignored
├── state.json # Session state - git-ignored
├── memories/ # Memory files - git-tracked
│ └── mem_abc123.md
└── archives/ # Archived content - git-tracked
The MCP server runs in an ephemeral container using stdio transport:
- A new container starts when Claude Code connects
- The container is automatically removed when the session ends (
--rmflag) - Container names are randomly generated by podman/docker
- No persistent container management required
Add these to your project's .gitignore:
.claude/ltm/stats.json
.claude/ltm/state.json
Memories (.claude/ltm/memories/ and .claude/ltm/index.json) are git-tracked for team sharing.
| Command | Description |
|---|---|
/ltm:init |
Add LTM integration instructions to project's CLAUDE.md |
/ltm:remember |
Store a memory interactively |
/ltm:remember <topic> |
Store a memory with the given topic |
/ltm:recall <query> |
Search memories by keyword |
/ltm:forget <id> |
Delete a memory by ID |
/ltm:status |
Show system status |
/ltm:help |
Show command summary |
/ltm:list |
List all memories |
/ltm:list --tag <tag> |
List memories with specific tag |
/ltm:check |
Check system integrity |
/ltm:fix |
Fix integrity issues |
/ltm:fix --clean-archives |
Fix issues and remove orphaned archives |
# Store a debugging solution
/ltm:remember Fix for authentication timeout
# Search for database-related memories
/ltm:recall database
# List all memories tagged with "api"
/ltm:list --tag api
# Check system health
/ltm:check
# Fix any integrity issues
/ltm:fixThe LTM MCP server provides these tools (usable directly or via slash commands):
| Tool | Parameters | Description |
|---|---|---|
store_memory |
topic, content, tags?, auto_tag? |
Store a new memory |
recall |
query, limit? |
Search memories |
list_memories |
phase?, tag?, keyword? |
List with filters |
get_memory |
id |
Get full memory content |
forget |
id |
Delete a memory |
ltm_status |
- | Get system status |
ltm_check |
- | Check integrity |
ltm_fix |
archive_orphans?, clean_orphaned_archives? |
Fix integrity issues |
claude-ltm/ # Plugin root
├── .claude-plugin/
│ └── plugin.json # Plugin manifest
├── .mcp.json # MCP server configuration
├── hooks/
│ ├── hooks.json # Hook definitions
│ ├── session_start.sh # Load memories on session start
│ ├── post_tool_use.sh # Track difficulty
│ ├── pre_compact.sh # Save state before compaction
│ └── session_end.sh # Persist and run eviction
├── commands/ # Slash commands
│ ├── remember.md
│ ├── recall.md
│ ├── forget.md
│ ├── status.md
│ ├── list.md
│ ├── check.md
│ ├── fix.md
│ └── help.md
├── server/ # Container source
│ ├── mcp_server.py
│ ├── store.py
│ ├── priority.py
│ ├── eviction.py
│ └── requirements.txt
├── scripts/
│ └── run-mcp.sh # Container launcher
├── Dockerfile
├── README.md
└── LICENSE
- Ephemeral containers: A new container starts for each Claude Code session and is removed when the session ends
- Stdio transport: MCP communication uses standard input/output (not TCP)
- Data persistence: Memory data is stored in the project's
.claude/ltm/directory, which is mounted into the container - No port mapping: Since stdio is used, no network ports are exposed
.claude/ltm/
├── index.json # Lightweight index for fast lookup (git-tracked)
├── stats.json # Access statistics (git-ignored)
├── state.json # Session state (git-ignored)
├── memories/ # Individual memory files (git-tracked)
│ └── mem_abc123.md # Markdown with YAML frontmatter
└── archives/ # Evicted detailed content (git-tracked)
Memories are stored as markdown files with YAML frontmatter:
---
id: "mem_abc123"
topic: "Fix database connection timeout"
tags:
- database
- debugging
phase: 0
difficulty: 0.8
created_at: "2026-01-15T10:30:00Z"
---
## Problem
The database connection was timing out after 30 seconds...
## Solution
Increased the connection pool size from 5 to 20...Memories are prioritized using:
priority = (difficulty * 0.4) + (recency * 0.3) + (frequency * 0.3)
- Difficulty: Based on tool failures and context compaction
- Recency: Sessions since last access (session-based, not time-based)
- Frequency: How often the memory is accessed
When storage exceeds the threshold, low-priority memories are progressively reduced:
| Phase | Name | Content | Description |
|---|---|---|---|
| 0 | Full | Complete content | Original memory |
| 1 | Hint | Summary only | Detailed content archived |
| 2 | Abstract | One-line summary | Further reduced |
| 3 | Removed | Archived only | Deleted from active storage |
Archived content is preserved in .claude/ltm/archives/ and can be restored.
The LTM system is designed for GitOps workflows and multi-user collaboration:
Git-Tracked Files (shared across users/branches):
index.json- Lightweight index with memory metadatamemories/*.md- Memory content files (human-readable markdown)archives/*.md- Archived content from evicted memories
Git-Ignored Files (local to each user/machine):
stats.json- Access statistics (access count, last accessed, priority scores)state.json- Session state (session counter, configuration)
Why This Separation?
| Concern | Git-Tracked | Git-Ignored |
|---|---|---|
| Memory content | Yes | - |
| Tags and metadata | Yes | - |
| Access patterns | - | Yes |
| Priority scores | - | Yes |
| Session counter | - | Yes |
Benefits:
-
No Merge Conflicts on Volatile Data - Access counts and priorities are local, so different users won't conflict on these frequently-changing values.
-
Shared Knowledge Base - Memory content and tags are versioned, so team members can share learnings across branches and pull requests.
-
Fresh Start on Clone - When you clone or switch branches,
stats.jsonstarts empty. Priority scores are recalculated from difficulty (stored in memory files) combined with local access patterns. -
Human-Readable Conflicts - When memory content conflicts occur, they're in markdown format and easy to resolve manually.
Multi-User Workflow:
# User A stores a memory
/ltm:remember Fix for database timeout
# User A commits and pushes
git add .claude/ltm/memories/ .claude/ltm/index.json
git commit -m "Add database timeout fix memory"
git push
# User B pulls and gets the memory
git pull
# Memory is now available, with fresh local access statsBranch Merging:
Memories are typically additive - new memories get new IDs. When merging branches:
- New memories from both branches are preserved
index.jsonchanges are usually auto-mergeable- Memory file conflicts (rare) are human-readable markdown
If you get a merge conflict in index.json, here's how to resolve it:
Structure of index.json:
{
"version": 1,
"memories": {
"mem_abc123": { "topic": "...", "tags": [...], "phase": 0 },
"mem_def456": { "topic": "...", "tags": [...], "phase": 0 }
}
}Resolution steps:
-
Keep all memories from both sides - Each memory has a unique ID (
mem_XXXXXXXX). Include all memory entries from both versions. -
Verify memory files exist - Each memory ID in
index.jsonshould have a corresponding.claude/ltm/memories/mem_XXXXXXXX.mdfile.
Example conflict resolution:
// CONFLICT - both sides added different memories
<<<<<<< HEAD
"memories": {
"mem_abc123": { "topic": "Fix timeout", "tags": ["database"], "phase": 0 }
}
=======
"memories": {
"mem_def456": { "topic": "API auth", "tags": ["api"], "phase": 0 }
}
>>>>>>> feature-branch
// RESOLVED - keep both memories
"memories": {
"mem_abc123": { "topic": "Fix timeout", "tags": ["database"], "phase": 0 },
"mem_def456": { "topic": "API auth", "tags": ["api"], "phase": 0 }
}After resolving: Run /ltm:check in Claude Code to verify integrity, and /ltm:fix if needed.
Configuration is stored in .claude/ltm/state.json:
{
"config": {
"max_memories": 100,
"memories_to_load": 10,
"eviction_batch_size": 10
}
}| Setting | Default | Description |
|---|---|---|
max_memories |
100 | Maximum memories before eviction |
memories_to_load |
10 | Memories loaded at session start |
eviction_batch_size |
10 | Memories processed per eviction cycle |
For contributing or testing changes locally:
# Clone the repository
git clone https://github.com/JoshSalomon/claude-ltm.git
cd claude-ltmWhen modifying the MCP server code, build and test with a local container:
# Build local image
podman build -t localhost/ltm-mcp-server:latest .
# Or: docker build -t localhost/ltm-mcp-server:latest .
# Run Claude Code with the local image
LTM_MCP_IMAGE=localhost/ltm-mcp-server:latest claude --plugin-dir .# Set up Python environment
python -m venv .venv
source .venv/bin/activate
pip install -r server/requirements.txt
pip install pytest pytest-cov pytest-asyncio
# Run tests
pytest server/tests/
# Run with coverage
pytest server/tests/ --cov=server --cov-report=term-missingclaude-ltm/
├── .claude-plugin/ # Plugin manifest
├── server/ # MCP server source
│ ├── mcp_server.py
│ ├── store.py
│ ├── priority.py
│ ├── eviction.py
│ ├── token_counter.py
│ ├── requirements.txt
│ └── tests/ # Test suite
│ ├── conftest.py
│ ├── test_store.py
│ ├── test_priority.py
│ ├── test_eviction.py
│ ├── test_mcp_server.py
│ └── test_token_counter.py
├── hooks/ # Hook scripts
│ ├── hooks.json
│ └── *.sh
├── commands/ # Slash commands
│ └── *.md
├── scripts/ # Utility scripts
│ └── run-mcp.sh
├── docs/ # Documentation
│ ├── ARCHITECTURE.md
│ ├── PRD.md
│ └── TESTING.md
├── Dockerfile
└── README.md
The MCP server runs in an ephemeral container. If it's not responding:
# Check if the container image exists
podman images | grep ltm-mcp-server
# Pull the latest image
podman pull quay.io/jsalomon/ltm-mcp-server:latest
# Test the container manually
echo '{}' | podman run -i --rm --userns=keep-id \
-v "$(pwd)/.claude/ltm:/data:Z" \
quay.io/jsalomon/ltm-mcp-server:latest# In Claude Code, check for issues
/ltm:check
# Fix any issues found
/ltm:fixIf using podman and encountering permission errors:
# The run-mcp.sh script uses --userns=keep-id automatically
# If running manually, include this flag:
podman run -i --rm --userns=keep-id -v "$(pwd)/.claude/ltm:/data:Z" quay.io/jsalomon/ltm-mcp-server:latestTo completely reset the LTM system:
# Remove all memories and state
rm -rf .claude/ltm/memories/*
rm -rf .claude/ltm/archives/*
rm -f .claude/ltm/index.json
rm -f .claude/ltm/stats.json
rm -f .claude/ltm/state.jsonContributions are welcome! Please read the documentation in docs/ before contributing.
Apache 2.0. See LICENSE file for details.