Skip to main content

Episodic memory for Claude Code - persistent memory of code written across sessions

Project description

AI-AfterImage

Tests codecov PyPI Python 3.10+ License: MIT

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:

  1. Pre-Write: Searches KB for related past code before writing
  2. Injects: "You wrote this before..." with relevant examples
  3. 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:

  1. PostgreSQL server unreachable
  2. psycopg package not installed
  3. Authentication failure
  4. 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_memory

    • id: Unique identifier
    • file_path: Where the code was written
    • old_code: Previous content (for Edit) or NULL (for Write)
    • new_code: The code that was written
    • context: Conversation context (why it was written)
    • timestamp: When it was written
    • session_id: Which Claude Code session
    • embedding: 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:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Submit a pull request

Name

AI = After Image

The ghost of what was written, persisting across sessions.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ai_afterimage-0.2.0.tar.gz (72.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ai_afterimage-0.2.0-py3-none-any.whl (50.3 kB view details)

Uploaded Python 3

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

Hashes for ai_afterimage-0.2.0.tar.gz
Algorithm Hash digest
SHA256 02f463176bfae8889fac797d90a4ce4b4eeaf69959c2925933a551bdff3cad9f
MD5 56491fdb619fb6b0ba62af5dcacfb4d0
BLAKE2b-256 a1ef7b06a0c2326eb00cf62e338772009e88ac0910b6a91ec0bf753624564ffb

See more details on using hashes here.

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

Hashes for ai_afterimage-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f0f2189fb1b32d7ef94c819b0d4db9dda841bc47f56d834a0bf094d86ddac568
MD5 d6be42674250e17ab16c23429d0bed8e
BLAKE2b-256 ba9eb974b0788ed48a6febfe5704814daf48127dbc0a1a460634828933c61514

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page