Observed since 21 Oct, 09:50 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Episodic memory for Claude Code - persistent memory of code written across sessions with churn tracking
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.
Claude Code starts every session with amnesia. Even though transcripts exist with every Write/Edit ever made, Claude can't remember:
Users re-explain context. Claude rewrites similar solutions. Institutional knowledge is lost.
A Claude Code hook that:
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
session_meta session ID extractionresponse_item envelope format (function_call + custom_tool_call)apply_patch unified diff parsing for file additions and updatesAfterImage now has semantic code understanding - not just text embeddings.
Language Detection
AST Parsing (tree-sitter based)
Semantic Index
Install with code intelligence features:
pip install ai-afterimage[ast]
See docs/code_intelligence.md for full documentation.
AfterImage now includes intelligent context injection that:
Enable via ~/.afterimage/config.yaml:
semantic_chunking:
enabled: true
max_tokens: 2000
summarization:
enabled: true
summary_mode_threshold: 3
See docs/semantic_chunking.md for full documentation.
AfterImage now tracks file and function modification patterns:
| Tier | Edits (30d) | Warning Behavior |
|---|---|---|
| Gold | 0-2 | Warns before modification |
| Silver | 3-10 | Normal operation |
| Bronze | 11-20 | Churn velocity alerts |
| Red | 21+ | Excessive churn warnings |
# Show churn stats for a file
afterimage churn src/app.py
# Show function-level details
afterimage churn src/app.py --functions
# Show edit history
afterimage churn src/app.py --history
# List hotspots (most churned files)
afterimage hotspots --limit 10
# List files by tier
afterimage files --tier gold
afterimage files --tier red
Gold Tier Warning:
File: src/core/auth.py
Tier: GOLD (Stable - rarely changed)
This file is stable and rarely modified.
- Total edits: 2
- Last edit: 2026-01-01T10:30:00
Are you sure this change is necessary?
Retry your write if intentional.
Repetitive Function Warning:
File: src/handlers/api.py
Function: process_request()
This function has been modified 4 times in the last 24 hours.
Possible issues:
- Bug not fully fixed
- Requirements unclear
- Function needs redesign
Consider stepping back to understand the root cause.
pip install ai-afterimage
afterimage setup
This automatically:
~/.afterimage/ configuration~/.claude/hooks/~/.claude/settings.jsonStart Claude Code. AfterImage now works invisibly in the background:
afterimage stats # Check KB statistics
afterimage search "authentication" # Search your code memory
afterimage recent # See recent stored entries
# Basic install (SQLite backend - works out of the box)
pip install ai-afterimage
# With PostgreSQL support (for multi-user/concurrent access)
pip install ai-afterimage[postgresql]
If you want concurrent access (multiple Claude sessions) or shared team memory:
# 1. Install PostgreSQL and pgvector
# Ubuntu/Debian:
sudo apt install postgresql postgresql-contrib
# Then install pgvector extension (see https://github.com/pgvector/pgvector)
# 2. Create database
sudo -u postgres psql -c "CREATE USER afterimage WITH PASSWORD 'yourpassword';"
sudo -u postgres psql -c "CREATE DATABASE afterimage OWNER afterimage;"
sudo -u postgres psql -d afterimage -c "CREATE EXTENSION vector;"
# 3. Install with PostgreSQL support
pip install ai-afterimage[postgresql]
# 4. Set password environment variable (REQUIRED for PostgreSQL)
export AFTERIMAGE_PG_PASSWORD=yourpassword
# Add to shell config for persistence
echo 'export AFTERIMAGE_PG_PASSWORD=yourpassword' >> ~/.bashrc
# 5. Update config to use PostgreSQL backend
# Edit ~/.afterimage/config.yaml and add:
# storage:
# backend: postgresql
Important: The AFTERIMAGE_PG_PASSWORD environment variable must be set before running any afterimage commands when using PostgreSQL. Without it, authentication will fail.
See the PostgreSQL Backend section below for full configuration options.
git clone https://github.com/DragonShadows1978/AI-AfterImage.git
cd AI-AfterImage
pip install -e ".[embeddings]"
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
# 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
For better concurrent access and vector search performance, AfterImage v0.2.0+ supports PostgreSQL with pgvector as an alternative to SQLite.
| 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.
# 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
# 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
# With package
pip install -e ".[postgresql]"
# Or manually
pip install asyncpg psycopg[binary] pgvector
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
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) |
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:
The hook automatically falls back to SQLite if PostgreSQL is unavailable:
psycopg package not installedYou'll see in stderr: [AfterImage] PostgreSQL unavailable (...), falling back to SQLite
This ensures your code memory continues working even if PostgreSQL has issues.
| 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 |
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
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
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
| +-- language_detection/ # v0.5.0: Language detection
| | +-- detector.py # Content-based language identification
| | +-- signatures.py # Language patterns and confidence scoring
| +-- ast_parser/ # v0.5.0: Tree-sitter AST parsing
| | +-- parser.py # Parser factory
| | +-- python_parser.py
| | +-- javascript_parser.py
| | +-- rust_parser.py
| | +-- go_parser.py
| | +-- c_parser.py
| +-- semantic_index/ # v0.5.0: IDE-like code intelligence
| +-- semantic_index.py # Main coordinator
| +-- definition_resolver.py # Go-to-definition
| +-- references_finder.py # Find all references
| +-- hover_provider.py # Hover info
| +-- symbol_table.py # Per-file symbols
| +-- call_graph.py # Call relationships
| +-- type_inference.py # Basic type propagation
+-- hooks/
| +-- README.md # Hook installation guide
| +-- afterimage_hook.py # Claude Code hook script
+-- docs/
| +-- code_intelligence.md # Code intelligence documentation
| +-- semantic_chunking.md # Semantic chunking documentation
| +-- churn_tracking.md # Churn tracking documentation
+-- tests/
+-- pyproject.toml
+-- README.md
+-- LICENSE
kb.py)SQLite database with FTS5 full-text search:
Table: code_memory
id: 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
filter.py)Determines if a file is "code" vs artifacts:
embeddings.py)Local embeddings using sentence-transformers:
search.py)Combines keyword and semantic search:
relevance = (fts_weight * fts_score) + (semantic_weight * semantic_score)
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
#### 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
min_query_tokens: 24
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
AfterImage is designed to work completely offline after the initial setup. This ensures your code memory is always accessible, even without internet.
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
After the model is cached, all operations work offline:
| Component | Location | Size |
|---|---|---|
| Knowledge Base | ~/.afterimage/memory.db | Varies |
| Embedding Model | ~/.afterimage/models/ | ~90MB |
| Configuration | ~/.afterimage/config.yaml | <1KB |
# 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"
| Operation | Target | Typical |
|---|---|---|
| Model load | <5s | 2-3s |
| Embedding generation | <50ms | 20-30ms |
| Hybrid search | <100ms | 30-50ms |
| FTS search only | <10ms | 2-5ms |
# 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
The test suite covers:
MIT License - See LICENSE for details.
Working implementation - Core functionality complete:
Planned features for upcoming releases:
Contributions welcome! Please:
AI = After Image
The ghost of what was written, persisting across sessions.
Stores hash new_code with SHA-256 after stripping trailing whitespace on each
line. Repeating a file's latest content returns its existing ID and increments
seen_count / last_seen; changed content creates a linked revision with
unit_key (currently the file path), version, supersedes, and is_latest.
Lexical, vector, hybrid, and path searches return current revisions by default;
use include_history=True to inspect historical matches.
Before searching an existing database, run the additive backfill:
python3 -m afterimage migrate_ai2a --dry-run
python3 -m afterimage migrate_ai2a --batch-size 500
# An explicit SQLite fixture/database can be selected with --db PATH.
The dry run writes nothing. Backfill preserves every legacy row, including duplicates, orders each file's history by timestamp (then ID for ties), and can resume committed batches. Repeating a completed migration updates zero rows. New stores finish any pending backfill for their own file before appending. Equal supplied timestamps for distinct content advance by a microsecond to preserve the legacy uniqueness constraint.
The Claude Code hook records known Bash test outcomes and groups edits to each
function until the next test outcome in that session. Red outcomes count as
failed attempts. Returning within a 0.95 difflib.SequenceMatcher ratio to a
body older than the immediate predecessor also counts as an immediate failed
attempt; a later red test is a separate attempt. A green test resets the streak
for functions touched in that session. At the threshold, the next Write/Edit
gets one loud REASSESS denial listing attempt times and kinds. The same streak
is shown once per function per session; retries proceed. Unknown test output
creates no outcome and leaves edits pending. Payload exit codes take priority
over output parsing.
Python names use AST scope (Class.method); an unknown name records at FILE
level with function_name = NULL. Warning exclusions suppress tier/repetitive
warnings while retaining edit records. They never suppress reassess.
Configure these keys under hook in ~/.afterimage/config.yaml:
hook:
reassess_threshold: 5
test_commands: [pytest, cargo test, npm test, npx jest, vitest, go test, ctest]
warning_noise_exclusions: [tests/, "test_*.py", "*_test.*", workspace/, /tmp/, WorkTree/, Fork/]
Test commands match command tokens, including python -m pytest, command paths,
environment assignments, and shell command chains. The list replaces the defaults
when supplied. Directory entries ending in / match path components; other
entries are globs matched against the full path or basename. Reassess state is
stored additively in the local ~/.afterimage/churn.db, with either memory backend.
The lead deploys hooks/afterimage_hook.py and adds a PostToolUse matcher for
Bash to the existing Write/Edit hook registration.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.8.0 | 4 Oct 2026 | 23 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Observed since 21 Oct, 09:50 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:b54df145fe27e87a7f0b003014ea007ec08304a84787693d3612acc548f73a3b