Episodic memory for Claude Code - persistent memory of code written across sessions
Project description
AI-AfterImage
Episodic memory for AI coding agents.
Like the visual phenomenon where an image persists after you look away - AfterImage gives Claude Code persistent memory of code it has written across sessions.
The Problem
Claude Code starts every session with amnesia. Even though transcripts exist with every Write/Edit ever made, Claude can't remember:
- What code it wrote yesterday
- How it solved a similar problem last week
- Patterns it has used before in this codebase
Users re-explain context. Claude rewrites similar solutions. Institutional knowledge is lost.
The Solution
A Claude Code hook that:
- Pre-Write: Searches KB for related past code before writing
- Injects: "You wrote this before..." with relevant examples
- Post-Write: Extracts and stores the diff for future recall
Write/Edit hook fires
|
v
Is this code?
(not .md/.json/etc)
|
v
Search KB for similar
|
+----+----+
Found Not Found
| |
v v
Inject Just write
context
| |
+-----+-----+
v
Claude writes
|
v
Extract diff
Store in KB
Features
- Claude Code hook (pre/post Write/Edit)
- Local SQLite + embeddings KB
- Hybrid search (keyword + semantic)
- Personal developer memory
- Session-to-session continuity
- No cloud, no API calls - everything local
- Works offline after initial model download
- CLI for search and management
- v0.2.0: Optional PostgreSQL backend with pgvector
Installation
From Source
# Clone the repository
git clone https://github.com/DragonShadows1978/AI-AfterImage.git
cd AI-AfterImage
# Install base package
pip install -e .
# Install with embedding support (recommended)
pip install -e ".[embeddings]"
Requirements
- Python 3.10+
- SQLite (built-in)
- sentence-transformers (optional, for semantic search)
Quick Start
1. Initialize Configuration
afterimage config --init
This creates ~/.afterimage/config.yaml with default settings.
2. Install Claude Code Hook
Copy the hook to your Claude Code hooks directory:
mkdir -p ~/.claude/hooks
cp hooks/afterimage_hook.py ~/.claude/hooks/
chmod +x ~/.claude/hooks/afterimage_hook.py
Add the hook to your Claude Code settings (~/.claude/settings.json):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "/path/to/home/.claude/hooks/afterimage_hook.py"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "/path/to/home/.claude/hooks/afterimage_hook.py"
}
]
}
]
}
}
Important: Update the path to match your home directory (e.g., /home/username/.claude/hooks/afterimage_hook.py).
Set the AfterImage path environment variable (or edit the hook to set the path):
export AFTERIMAGE_PATH="$HOME/AI-AfterImage"
3. Ingest Existing Transcripts (Optional)
If you have existing Claude Code transcripts:
# Ingest all transcripts from default location
afterimage ingest
# Or from a specific directory
afterimage ingest -d /path/to/transcripts
# With verbose output
afterimage ingest -v
4. Search Your Memory
# Search for code you've written before
afterimage search "authentication middleware"
# Filter by file path
afterimage search "validate" --path validators
# Output as JSON
afterimage search "database connection" --json
PostgreSQL Backend (Optional)
For better concurrent access and vector search performance, AfterImage v0.2.0+ supports PostgreSQL with pgvector as an alternative to SQLite.
Why PostgreSQL?
| Feature | SQLite | PostgreSQL |
|---|---|---|
| Setup | Zero-config | Requires installation |
| Concurrent writes | Limited | Excellent |
| Vector search | Approximate (brute force) | Native pgvector with IVFFlat |
| Multi-user | Single user | Multi-user capable |
| Large datasets | Works well to ~100K entries | Scales to millions |
| Network access | Local only | Network capable |
Recommendation: Start with SQLite (default). Switch to PostgreSQL if you need concurrent access from multiple Claude Code sessions or have a large code history.
PostgreSQL Setup
1. Install PostgreSQL and pgvector
# Ubuntu/Debian
sudo apt install postgresql postgresql-contrib
# Install pgvector extension
sudo apt install postgresql-server-dev-all
git clone https://github.com/pgvector/pgvector.git
cd pgvector && make && sudo make install
2. Create the Database
# Create user and database
sudo -u postgres psql <<EOF
CREATE USER afterimage WITH PASSWORD 'your_secure_password';
CREATE DATABASE afterimage OWNER afterimage;
\c afterimage
CREATE EXTENSION vector;
EOF
3. Install Python Dependencies
# With package
pip install -e ".[postgresql]"
# Or manually
pip install asyncpg psycopg[binary] pgvector
4. Configure AfterImage
Edit ~/.afterimage/config.yaml:
storage:
backend: postgresql
sqlite:
path: ~/.afterimage/memory.db # Fallback location
postgresql:
host: localhost
port: 5432
database: afterimage
user: afterimage
# Password via environment variable (recommended)
# Or set here: password: your_secure_password
min_pool_size: 2
max_pool_size: 10
# Other settings remain the same
search:
max_results: 5
relevance_threshold: 0.6
embeddings:
model: all-MiniLM-L6-v2
device: cpu # or cuda
Environment Variables
Set the PostgreSQL password via environment variable (recommended over storing in config):
# Add to ~/.bashrc or ~/.zshrc
export AFTERIMAGE_PG_PASSWORD=your_secure_password
# Optional: Override backend from config
export AFTERIMAGE_BACKEND=postgresql
| Variable | Description |
|---|---|
AFTERIMAGE_BACKEND |
Override backend: sqlite or postgresql |
AFTERIMAGE_PG_PASSWORD |
PostgreSQL password |
AFTERIMAGE_PG_HOST |
PostgreSQL host |
AFTERIMAGE_PG_PORT |
PostgreSQL port |
AFTERIMAGE_PG_DATABASE |
Database name |
AFTERIMAGE_PG_USER |
Database user |
AFTERIMAGE_DATABASE_URL |
Full connection string (overrides individual params) |
Migrating from SQLite to PostgreSQL
If you have existing code memories in SQLite, migrate them to PostgreSQL:
# Migrate all entries
afterimage migrate --sqlite ~/.afterimage/memory.db --postgresql
# Validate migration
afterimage stats --backend postgresql
# Verify entry counts match
afterimage stats --backend sqlite
The migration:
- Copies all entries with embeddings preserved
- Supports resumable migration (skips existing entries)
- Validates embedding integrity
Graceful Fallback
The hook automatically falls back to SQLite if PostgreSQL is unavailable:
- PostgreSQL server unreachable
psycopgpackage not installed- Authentication failure
- Database doesn't exist
You'll see in stderr: [AfterImage] PostgreSQL unavailable (...), falling back to SQLite
This ensures your code memory continues working even if PostgreSQL has issues.
CLI Commands
| Command | Description |
|---|---|
afterimage search <query> |
Search the knowledge base |
afterimage ingest |
Ingest transcripts into KB |
afterimage stats |
Show KB statistics |
afterimage recent |
Show recent entries |
afterimage export |
Export KB to JSON |
afterimage clear |
Clear the knowledge base |
afterimage config |
Show/create configuration |
Search Options
afterimage search "query" [options]
Options:
-l, --limit N Maximum results (default: 5)
-t, --threshold FLOAT Minimum relevance (default: 0.3)
-p, --path PATTERN Filter by file path
--json Output as JSON
Ingest Options
afterimage ingest [options]
Options:
-f, --file PATH Specific transcript file
-d, --directory PATH Directory to search
--no-embeddings Skip embedding generation
-v, --verbose Verbose output
Architecture
AI-AfterImage/
+-- afterimage/
| +-- __init__.py
| +-- kb.py # Knowledge base (SQLite + FTS5)
| +-- filter.py # Code vs artifact filter
| +-- embeddings.py # Embedding generation (sentence-transformers)
| +-- search.py # Hybrid search (keyword + semantic)
| +-- inject.py # Context injection formatting
| +-- extract.py # Transcript parsing
| +-- cli.py # Command line interface
+-- hooks/
| +-- README.md # Hook installation guide
| +-- afterimage_hook.py # Claude Code hook script
+-- tests/
| +-- test_kb.py
| +-- test_filter.py
| +-- test_search.py
| +-- test_extract.py
| +-- test_inject.py
| +-- test_integration.py
+-- pyproject.toml
+-- setup.py
+-- README.md
+-- LICENSE
Core Components
Knowledge Base (kb.py)
SQLite database with FTS5 full-text search:
-
Table:
code_memoryid: Unique identifierfile_path: Where the code was writtenold_code: Previous content (for Edit) or NULL (for Write)new_code: The code that was writtencontext: Conversation context (why it was written)timestamp: When it was writtensession_id: Which Claude Code sessionembedding: Vector embedding (BLOB)
-
Location:
~/.afterimage/memory.db
Code Filter (filter.py)
Determines if a file is "code" vs artifacts:
- Code extensions: .py, .js, .ts, .jsx, .tsx, .rs, .go, .java, .c, .cpp, etc.
- Skip extensions: .md, .json, .yaml, .txt, .log, .env
- Skip paths: artifacts/, docs/, research/, node_modules/
- Content heuristics: Fallback for unknown extensions
Embedding System (embeddings.py)
Local embeddings using sentence-transformers:
- Model: all-MiniLM-L6-v2 (90MB)
- Runs locally: No API calls
- CUDA support: Uses GPU if available
- Cached: Embeddings cached in KB for fast retrieval
Hybrid Search (search.py)
Combines keyword and semantic search:
relevance = (fts_weight * fts_score) + (semantic_weight * semantic_score)
- FTS5: SQLite full-text search with BM25 scoring
- Semantic: Cosine similarity between embeddings
- Default weights: FTS 40%, Semantic 60%
Context Injection (inject.py)
Formats search results for Claude:
You have written similar code before (3 matches):
### Match 1 (src/validators.py)
```python
def validate_email(email):
return '@' in email
Context: Added email validation for user signup
Relevance: 85%
### Hook System
The Claude Code hook integrates AfterImage into your workflow using a **deny-then-allow pattern** that ensures Claude actually sees relevant past code before writing.
#### How It Works
Claude attempts Write/Edit | v AfterImage hook fires | v Search KB for similar code | +----+----+ Found Not Found | | v v DENY Allow
- show (write context proceeds) | v Claude SEES the past code (deny reason shown to Claude!) | v Claude retries Write | v Hook recognizes retry (same content hash) | v ALLOW | v File created | v Post-hook stores code in KB
#### Pre-Write Hook (Deny-Then-Allow)
The key insight: Claude Code's hook system shows `permissionDecisionReason` to Claude when a hook returns `deny`. We use this to inject context:
1. **First attempt**: Hook searches KB, finds similar code
2. **DENY** with reason containing the code examples
3. Claude **sees** the past code (it's in the deny message!)
4. Claude **retries** the write (automatically or adjusted)
5. **Second attempt**: Hook recognizes the same content hash, **allows** it
This ensures Claude has seen relevant patterns before the file is actually written.
#### Post-Write Hook
After Claude successfully writes code:
1. Check if file is code (not markdown, JSON, etc.)
2. Generate embedding (optional, for semantic search)
3. Store in KB with session context
## Configuration
`~/.afterimage/config.yaml`:
```yaml
# Search settings
search:
max_results: 5
relevance_threshold: 0.6
max_injection_tokens: 2000
# Filter settings
filter:
code_extensions:
- .py
- .js
- .ts
- .jsx
- .tsx
- .rs
- .go
- .java
- .c
- .cpp
- .h
- .rb
- .php
- .swift
- .kt
skip_extensions:
- .md
- .json
- .yaml
- .yml
- .txt
- .log
- .env
skip_paths:
- artifacts/
- docs/
- research/
- test_data/
- __pycache__/
- node_modules/
# Embedding model
embeddings:
model: all-MiniLM-L6-v2
device: cpu # or cuda
Offline Mode
AfterImage is designed to work completely offline after the initial setup. This ensures your code memory is always accessible, even without internet.
Initial Setup (Requires Network)
On first use with embeddings enabled, the sentence-transformers model (~90MB) is downloaded and cached:
# First use downloads the model to ~/.afterimage/models/
afterimage search "test" # Downloads all-MiniLM-L6-v2
Fully Offline After Setup
After the model is cached, all operations work offline:
- SQLite database - Local file storage, no network
- FTS5 search - Built into SQLite, no network
- Embeddings - Model loaded from local cache
- Configuration - Local YAML files only
What's Cached
| Component | Location | Size |
|---|---|---|
| Knowledge Base | ~/.afterimage/memory.db |
Varies |
| Embedding Model | ~/.afterimage/models/ |
~90MB |
| Configuration | ~/.afterimage/config.yaml |
<1KB |
Verifying Offline Readiness
# Check if model is cached
ls ~/.afterimage/models/models--sentence-transformers--all-MiniLM-L6-v2
# Test offline search (disconnect network first to verify)
afterimage search "function"
Performance
| Operation | Target | Typical |
|---|---|---|
| Model load | <5s | 2-3s |
| Embedding generation | <50ms | 20-30ms |
| Hybrid search | <100ms | 30-50ms |
| FTS search only | <10ms | 2-5ms |
Development
Running Tests
# Install dev dependencies
pip install -e ".[dev]"
# Run all tests
pytest
# Run with coverage
pytest --cov=afterimage
# Run specific test file
pytest tests/test_inject.py
# Run slow tests (with embeddings)
pytest -m slow
Test Coverage
The test suite covers:
- Knowledge Base operations
- Code filtering logic
- Transcript extraction
- Search functionality
- Context injection formatting
- End-to-end integration
- Hook script handling
License
MIT License - See LICENSE for details.
Status
Working implementation - Core functionality complete:
- Knowledge Base with FTS5
- Code filtering
- Transcript extraction
- Embedding generation
- Hybrid search
- Context injection
- CLI commands
- Claude Code hooks
- Test suite (163 tests, 88% coverage)
- Offline mode (works without network after model download)
- v0.2.0: PostgreSQL backend with pgvector (optional)
- v0.2.0: Graceful fallback from PostgreSQL to SQLite
- v0.2.0: Environment variable configuration
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Submit a pull request
Name
AI = After Image
The ghost of what was written, persisting across sessions.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ai_afterimage-0.2.0.tar.gz.
File metadata
- Download URL: ai_afterimage-0.2.0.tar.gz
- Upload date:
- Size: 72.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
02f463176bfae8889fac797d90a4ce4b4eeaf69959c2925933a551bdff3cad9f
|
|
| MD5 |
56491fdb619fb6b0ba62af5dcacfb4d0
|
|
| BLAKE2b-256 |
a1ef7b06a0c2326eb00cf62e338772009e88ac0910b6a91ec0bf753624564ffb
|
File details
Details for the file ai_afterimage-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ai_afterimage-0.2.0-py3-none-any.whl
- Upload date:
- Size: 50.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0f2189fb1b32d7ef94c819b0d4db9dda841bc47f56d834a0bf094d86ddac568
|
|
| MD5 |
d6be42674250e17ab16c23429d0bed8e
|
|
| BLAKE2b-256 |
ba9eb974b0788ed48a6febfe5704814daf48127dbc0a1a460634828933c61514
|