AI agents are amnesic. Every session starts from zero. Yesterday's hard-won lesson — the one that took twenty failed commands to learn — is gone this morning.
Pain Memory gives any AI agent a memory of its own failures — automatically detecting failures after each step, matching them against past pain, and injecting behavioral rules (problem → fix) that stop the repeat.
One engine, four ways to plug in: MCP server (Claude Code, Cursor, Codex CLI, Windsurf, Zed...), Claude Code hooks, a universal CLI wrapper, or a plain Python SDK.
Tool call → pre-filter (exit≠0? stderr?) → embed → cosine match verified pain
├─ hit → inject the behavioral rule into the agent's context
└─ miss + error → independent LLM judge → write new (speculative) memory
└─ Layer 2: clusters recurrences into pattern rules
└─ Layer 3: distills rules into principles → feed back
| Notes / RAG memory | Pain Memory | |
|---|---|---|
| Trigger | manual or chat-time | after every tool call |
| Content | information ("remember that...") | behavior rules (WHAT → HOW → WHY) |
| Trust | everything is kept | severity × recurrence × similarity; new memories start quarantined |
| Cost | main-session tokens | embedding + matching run out-of-band; zero main-session cost |
| Evolution | static | 3-layer self-evolution: memories → patterns → principles |
The hard problem isn't remembering — it's knowing whether to trust what you remember. That's why memory writing is gated by an independent LLM judge, unverified memories are quarantined until they recur 3 times, and write-side dedup turns near-identical failures into recurrence events instead of duplicates.
pip install pain-memory # numpy is the only hard dependency
pm init --with-seeds # optional: 8 hand-verified starter memoriesclaude mcp add pain-memory -- pain-memory-mcp
# Cursor/Codex/Windsurf: point the MCP server command at `pain-memory-mcp`The agent gets pain_check (after each step), pain_retrieve (at task start), pain_report, pain_add, pain_stats, pain_grow, pain_mine — memories are scoped per project automatically.
pm install-hooks --claude-dir ~/.claudeThis registers a PostToolUse hook (detect + inject after every tool call) and a UserPromptSubmit hook (task-entry retrieval). No agent cooperation needed — the memory loop just runs.
pm exec -- npm run build # runs the command, detects pain, injects on stderr
pm exec -- python train.py # exit code is propagated unchangedfrom painmemory import open_engine, Observation
engine = open_engine()
injection = engine.check(Observation(
tool="Bash", command="npm run build",
exit_code=1, stderr="npm ERR! missing script"))
if injection:
print(injection.text) # behavior rule(s) to show the agent| Layer | Runs | What it does |
|---|---|---|
| 1 · Detect & inject | every tool call | pre-filter → embed → cosine-match verified memories → inject rules; unmatched errors → LLM judge → write-back |
| 2 · Pattern grower | pm grow / pain_grow |
clusters chains with pairwise cosine > 0.82; ≥3 members condense into a pattern rule with confidence = mean pairwise similarity |
| 3 · Principle miner | pm mine / pain_mine |
merges rules into principles (cosine > 0.85) or creates new ones; expands principle coverage; principles re-enter Layer 1 retrieval at 2× weight |
pm stats # store health
pm list # all memories with severity × recurrence
pm log # activity: inject / judge / dedup events
pm grow && pm mine # run layers 2+3 manuallyEvery memory carries a confidence chain:
- verified — human-confirmed (seeds,
pm add, MCPpain_add) - speculative — written by the LLM judge; quarantined from task-entry retrieval until it recurs 3× (frequency substitutes for verification)
- dedup — a near-identical failure (cosine > 0.88) appends a recurrence event instead of creating a duplicate
Injection is always labeled with similarity, trust, and recurrence — the agent decides, never blindly obeys.
Embeddings — tried in order, first healthy wins: local persistent server (BGE, ~50ms/embed) → OpenAI-compatible API → Ollama → sentence-transformers → char-ngram hashing (zero-dep lexical semantics, multilingual) → deterministic hash mock (always works).
LLM judge — any OpenAI-compatible endpoint:
# painmemory.toml
[judge]
base_url = "https://api.deepseek.com/v1" # or OpenAI, GLM, vLLM, ...
model = "deepseek-chat"
[embedder]
model = "BAAI/bge-base-zh-v1.5"
locale = "en" # "zh" restores the original Chinese injection styleEnvironment equivalents: PAIN_MEMORY_JUDGE_BASE_URL / PAIN_MEMORY_JUDGE_MODEL / PAIN_MEMORY_JUDGE_API_KEY (or the DEEPSEEK_API_KEY shortcut). No judge configured? Memory injection still works; nothing new is written.
Memories live in ~/.painmemory/stores/<scope>/ — the scope is a stable hash of the project root, so different projects never pollute each other. Override with PAIN_MEMORY_SCOPE, or point PAIN_MEMORY_HOME anywhere. The file formats are backward compatible with the original ~/.claude/tools/pain-memory/memory directory.
painmemory/
├── engine.py # Layer 1: check() / retrieve() / write-back
├── embedder.py # provider chain incl. zero-dep n-gram backend
├── judge.py # independent LLM judge (OpenAI-compatible)
├── prefilter.py # exit-code / stderr / hard-error detection
├── grower.py # Layer 2: cluster → pattern rules
├── miner.py # Layer 3: rules → principles
├── store.py # JSON + npz persistence, per-project scoping
├── config.py # defaults < toml < env < args
├── cli.py # pm ...
└── adapters/
├── mcp_server.py # stdio MCP server (stdlib-only JSON-RPC)
├── claude_code.py # PostToolUse / UserPromptSubmit hooks
└── exec_runner.py # pm exec -- <any command>
examples/demo.py runs the whole loop offline — no API keys, no model downloads:
python examples/demo.pypip install -e .[dev]
pytest # 45 tests, fully offlineDesign history and the complete architecture document: DESIGN.md. Chinese readme: README.zh-CN.md.
MIT