# ai-memory-core — Persistent AI Memory System

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A lightweight Rust library that gives AI applications long-term memory — persist, inject, and extract knowledge across conversation sessions.

## Features

| Capability | Description |
|---|---|
| **Spatial Metaphor** | Wing/Hall/Room 3-tier path classification via declarative macros |
| **Pure Markdown Storage** | `memories.md` file as the database — human-readable and editor-friendly |
| **Metadata Tracking** | MD5 content fingerprinting + cross-session access statistics |
| **Token-Budgeted Injection** | 2000 token budget with priority-based sorting into system prompts |
| **LLM Extraction** | Post-session summarization via Anthropic / OpenAI / DeepSeek APIs |
| **Zero Heavy Dependencies** | Core: only `serde`, `serde_json`, `chrono`, `dirs`, `md-5` |

## Architecture

```
                 ┌──────────────────────┐
                 │    Memory System     │
                 └──────────┬───────────┘
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
   ┌────▼────┐        ┌────▼────┐        ┌─────▼─────┐
   │ Hot Memory│       │ Cold Memory│      │  Metadata  │
   │ (Inject)  │       │  (Store)   │      │  (Track)   │
   └────┬────┘        └────┬────┘        └─────┬─────┘
        │                   │                   │
   ┌────▼────┐        ┌────▼────┐        ┌─────▼─────┐
   │ Session │        │    IO    │        │   Meta    │
   │ Prompt  │        │ File IO  │        │  Store    │
   └─────────┘        └─────────┘        └───────────┘
        │                   │                   │
        └───────────────────┼───────────────────┘
                            │
                    ┌───────▼───────┐
                    │   Extractor   │
                    │  (LLM-based)  │
                    └───────────────┘
```

### Spatial Metaphor (3-Tier Classification)

```
Wing (Scope)     Hall (Category)         Room (Sub-category)
─────────────────────────────────────────────────────────
project ────┬─── Preferences ───────┬─── programming-languages
            │                       ├─── editor-config
            │                       └─── communication-style
            │
            ├─── Project Knowledge ─┬─── frontend-stack
            │                       └─── database-schema
            │
            ├─── Decisions ─────────┬─── architecture
            │                       └─── technology-choice
            │
            └─── Workflow Patterns ─┬─── code-review
                                    └─── deployment

user ───────┬─── Preferences ───────┬─── communication-style
            └─── Project Knowledge ─┬─── personal-tools
```

## Quick Start

```toml
[dependencies]
ai-memory-core = "0.1"
```

### Basic Usage

```rust
use std::path::PathBuf;
use ai_memory_core::{init, handle_memory_save, load_memories, inject_memories_into_system_prompt};

fn main() {
    // 1. Initialize data directory (defaults to ~/.ai-memory/ if not called)
    init(PathBuf::from("/path/to/data"));

    // 2. Save memories with spatial paths
    handle_memory_save(
        "Preferences/programming-languages",
        "Prefers TypeScript and Rust"
    ).unwrap();

    handle_memory_save(
        "Decisions/database-choice",
        "Uses PostgreSQL as primary database"
    ).unwrap();

    // 3. Load all memories
    if let Some(memories) = load_memories() {
        println!("{}", memories);
    }

    // 4. Inject into system prompt (auto-orders by priority, capped at 2000 tokens)
    let system_prompt = "You are a helpful assistant.";
    let enriched = inject_memories_into_system_prompt(system_prompt);
    // Result: "You are a helpful assistant.\n\n[USER_MEMORY]\n\nPrefers TypeScript...\n\n[/USER_MEMORY]"
}
```

### LLM Memory Extraction (requires `llm-extraction` feature)

```toml
[dependencies]
ai-memory-core = { version = "0.1", features = ["llm-extraction"] }
```

```rust
use ai_memory_core::{extract_memories_with_llm, build_extraction_prompt};

async fn auto_extract_after_session(api_key: &str, conversation: &str) {
    // 1. Build extraction prompt from conversation
    let prompt = build_extraction_prompt(conversation);

    // 2. Call LLM to extract and auto-save memories
    let count = extract_memories_with_llm(
        api_key,
        conversation,
        "anthropic"  // or "openai" / "deepseek"
    ).await.unwrap();

    println!("Extracted {} memories from this session", count);
}
```

### Customizing Categories (via macros)

Extend the default classifications by adding entries to the declarative macros:

```rust
declare_wings! {
    Project : "project";
    User : "user";
    Team : "team";  // Add team scope
}

declare_halls! {
    Preferences : "Preferences";
    ProjectKnowledge : "Project Knowledge";
    Decisions : "Decisions";
    WorkflowPatterns : "Workflow Patterns";
    CodingStandards : "Coding Standards";  // Add coding standards
}
```

## API Reference

### Configuration (`config`)

| Function | Description |
|---|---|
| `init(dir: PathBuf)` | Set custom data directory |
| `memories_file() -> PathBuf` | Get memory file path |
| `metadata_file() -> PathBuf` | Get metadata file path |

### Spatial Categories (`categories`)

| Type / Function | Description |
|---|---|
| `Wing` enum | Project / User scope |
| `MemoryHall` enum | Preferences / ProjectKnowledge / Decisions / WorkflowPatterns |
| `MemoryPath` struct | Parsed spatial path (wing, hall, room) |
| `path_schema() -> Value` | JSON Schema for tool definitions |

### File I/O (`io`)

| Function | Description |
|---|---|
| `load_memories() -> Option<String>` | Load memories file (returns None if not found) |
| `save_memories(content) -> Result` | Save memories file |
| `append_to_section(memories, section, entry) -> String` | Append entry to section with dedup |
| `ensure_memories_file() -> Result<PathBuf>` | Create file if it doesn't exist |
| `format_initial_memories(section, entry) -> String` | Format a new memories document |

### Metadata (`meta`)

| Type / Function | Description |
|---|---|
| `MetadataStore::load()` | Load metadata from disk (auto-degrades on failure) |
| `MetadataStore::save()` | Persist metadata to disk |
| `MetadataStore::track_access(fingerprint)` | Record a memory access |
| `MetadataStore::high_value_memories()` | Get memories with access_count >= 5 |
| `content_fingerprint(content) -> String` | Compute MD5 fingerprint (32-char hex) |

### Memory Save Tool (`tool`)

| Function | Description |
|---|---|
| `handle_memory_save(path, content) -> Result<String>` | Parse path, validate content, save to file |
| `memory_save_schema() -> Value` | JSON Schema for tool integration |

### Session Injection (`session`)

| Function | Description |
|---|---|
| `load_memories_for_injection() -> String` | Load and format memories (sorted by priority, capped at 2000 tokens) |
| `inject_memories_into_system_prompt(prompt) -> String` | Inject formatted memories into system prompt |

### Memory Extraction (`extractor`)

| Function | Description |
|---|---|
| `build_extraction_prompt(summary) -> String` | Build LLM extraction prompt (supports external templates) |
| `parse_extraction_result(result) -> Vec<ExtractedMemory>` | Parse LLM output into structured memories |
| `save_extracted_memories(memories) -> Result<usize>` | Save extracted memories to file |
| `extract_and_save_memories(result) -> Result<usize>` | Full pipeline: parse + save |
| `extract_memories_simple(summary) -> Result<usize>` | Demo-mode extractor (prints summary, saves nothing) |
| `extract_memories_with_llm(key, summary, provider) -> Result<usize>` | Full LLM extraction (requires `llm-extraction` feature) |
| `clear_prompt_cache()` | Clear cached extraction prompt template |

## Memory File Format

```markdown
# User Memories
# Last updated: 2025-05-09

## Preferences
### programming-languages
- [2025-05-09] Prefers TypeScript and Rust
- [2025-05-08] Uses VS Code editor

## Decisions
### database-choice
- [2025-05-09] Uses PostgreSQL as primary database
```

## Deduplication

When saving, the system extracts the entry content (ignoring the date prefix) and compares against existing entries in the same section. If a duplicate is found, the date is updated rather than adding a new entry.

## External Prompt Templates

The extraction prompt can be customized by placing a template file at `data_dir/prompts/memory/extract.md`:

```markdown
You are a specialized memory extraction assistant. Extract from the conversation:

## Extraction Rules
1. User preferences: ...
2. Important decisions: ...

## Format
**[Category]** one-line memory content

## Conversation
{conversation_summary}
```

Use `{conversation_summary}` as the placeholder for the conversation text. Call `clear_prompt_cache()` to force reload after template changes.

## Testing

```bash
cargo test
```

## Documentation

- **[Usage Manual (中文)](docs/USAGE_zh.md)** — Comprehensive guide with integration examples, best practices, and troubleshooting.

## Feature Flags

| Flag | Dependencies | Description |
|---|---|---|
| `default` | — | All core features (IO, categories, metadata, injection, tool) |
| `llm-extraction` | `reqwest` | Async LLM-based memory extraction |

## License

MIT
