Metadata-Version: 2.4
Name: eidetic-ai
Version: 0.2.0
Summary: Production-grade long-term memory system for AI conversations
Author: phraakture
License-Expression: MIT
Project-URL: Homepage, https://github.com/phraakture/eidetic
Project-URL: Repository, https://github.com/phraakture/eidetic
Project-URL: Issues, https://github.com/phraakture/eidetic/issues
Keywords: ai,memory,llm,openai,context,conversation,long-term-memory
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic-settings>=2.7
Requires-Dist: structlog>=0.25
Requires-Dist: typing-extensions>=4.12
Requires-Dist: sqlalchemy[asyncio]>=2.0
Requires-Dist: aiosqlite>=0.20
Requires-Dist: httpx>=0.28
Requires-Dist: faiss-cpu>=1.9
Requires-Dist: numpy>=2.0
Provides-Extra: dev
Requires-Dist: alembic>=1.14; extra == "dev"
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.25; extra == "dev"
Requires-Dist: ruff>=0.9; extra == "dev"
Requires-Dist: mypy>=1.15; extra == "dev"
Provides-Extra: server
Requires-Dist: fastapi>=0.139; extra == "server"
Requires-Dist: uvicorn>=0.30; extra == "server"
Provides-Extra: postgres
Requires-Dist: asyncpg>=0.29; extra == "postgres"
Dynamic: license-file

# Eidetic

Production-grade long-term memory system for AI conversations. Extracts
semantic facts and episodic bubbles from conversation turns, stores them in a
FAISS vector index with SQLite persistence, and surfaces them via a simple
Python API or FastAPI HTTP server.

## Features

- **Dual Memory Types**: Semantic facts (stable, long-term) + Episodic bubbles (time-bound moments)
- **Fast Search**: FAISS-powered vector similarity search
- **Smart Updates**: LLM-based contradiction detection (ADD / UPDATE / REPLACE / DELETE / NOOP)
- **Memory Connections**: Bubbles auto-link to related facts
- **Multi-Provider**: OpenAI or OpenRouter (Claude, Gemini, etc.)
- **Flexible Storage**: SQLite (default) or PostgreSQL
- **Full async**: Python 3.12+, SQLAlchemy 2.0 asyncio

## Installation

```bash
pip install eidetic-ai

# For the HTTP server (optional):
pip install 'eidetic-ai[server]'

# For PostgreSQL (optional):
pip install 'eidetic-ai[postgres]'
```

## Quick Start (Library)

```python
import asyncio
from eidetic import configure, Memory, create_table

# Configure with your API key
configure(openai_api_key="sk-...")

# Create tables (first time only)
async def main():
    await create_table()

    memory = Memory()

    # Add memories from a conversation turn
    result = await memory.add(
        messages=[
            {"role": "user", "content": "Hi, I'm Alice and I love Python"},
            {"role": "assistant", "content": "Nice to meet you, Alice!"},
        ],
        conversation_id=1,
    )
    print(result)
    # {'semantic': ['User is named Alice', 'User loves Python'], 'bubbles': []}

    # Search stored memories
    results = await memory.search(
        query="What programming language?",
        conversation_id=1,
        limit=5,
    )
    for r in results["results"]:
        print(f"  [{r['type']}] {r['memory']} (score: {r['score']})")

asyncio.run(main())
```

### Environment Variables

```bash
export EIDETIC_OPENAI_API_KEY="sk-..."
# Or for OpenRouter:
export EIDETIC_OPENROUTER_API_KEY="sk-or-v1-..."
export EIDETIC_LLM_PROVIDER="openrouter"
export EIDETIC_DATABASE_URL="sqlite+aiosqlite:///path/to/eidetic.db"
```

## Full Example: Chat with Memory

```python
import asyncio
from openai import OpenAI
from eidetic import configure, Memory, create_table

configure(openrouter_api_key="sk-or-v1-...", llm_provider="openrouter")
chat = OpenAI(api_key="sk-or-v1-...", base_url="https://openrouter.ai/api/v1")

async def main():
    await create_table()
    memory = Memory()

    def chat_with_memories(message: str, conv_id: int = 1) -> str:
        # 1. Search relevant memories
        results = asyncio.run(memory.search(query=message, conversation_id=conv_id, limit=5))
        memories_str = "\n".join(
            f"- [{r['type']}] {r['memory']}"
            for r in results["results"]
        )

        # 2. Build prompt with memories
        system = f"You are a helpful AI.\n\nUser Memories:\n{memories_str or 'None yet.'}"

        # 3. Call LLM
        response = chat.chat.completions.create(
            model="anthropic/claude-sonnet-4.5",
            messages=[
                {"role": "system", "content": system},
                {"role": "user", "content": message},
            ],
        )
        reply = response.choices[0].message.content

        # 4. Store new memories
        asyncio.run(memory.add(
            messages=[{"role": "user", "content": message}, {"role": "assistant", "content": reply}],
            conversation_id=conv_id,
        ))
        return reply

    while True:
        msg = input("You: ")
        if msg.lower() == "exit":
            break
        print(f"AI: {chat_with_memories(msg)}")

asyncio.run(main())
```

## HTTP Server

```bash
pip install 'eidetic[server]'

# Start the API server
eidetic serve
```

### API Endpoints

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/health` | Health check |
| `POST` | `/conversations` | Create a conversation |
| `POST` | `/memories` | Add memories from a conversation turn |
| `GET` | `/memories/search` | Search memories by similarity |
| `PUT` | `/memories/{id}` | Update a memory's text |
| `DELETE` | `/memories/{id}` | Soft-delete a memory |
| `POST` | `/bubbles/expire` | Run bubble TTL expiry |

Full OpenAPI docs at `/docs` when the server is running.

### Docker

```bash
docker compose up --build
```

Set API keys via environment variables (see `.env.example`).

## CLI

```
eidetic create-conversation           create a new conversation
eidetic add <conv_id> <user> <asst>   add memories from one turn
eidetic search <conv_id> <query>      search stored memories
eidetic expire-bubbles [conv_id]      run bubble TTL expiry
```

## Architecture

```
┌─────────────┐    ┌──────────────┐    ┌──────────────┐
│  FastAPI    │───▶│ MemoryService │───▶│  Extractor   │
│  (HTTP)     │    │  (orchestr.) │    │  (LLM)       │
└─────────────┘    └──────┬───────┘    └──────────────┘
                          │
             ┌────────────┼────────────┐
             ▼            ▼            ▼
      ┌──────────┐ ┌──────────┐ ┌──────────┐
      │Classifier│ │  Bubble  │ │ Summary  │
      │ (LLM)    │ │  Creator │ │ Service  │
      └──────────┘ └──────────┘ └──────────┘
                          │
             ┌────────────┼────────────┐
             ▼            ▼            ▼
      ┌──────────┐ ┌──────────┐ ┌──────────┐
      │  FAISS   │ │ SQLite   │ │  LLM /   │
      │  Index   │ │   DB     │ │ Embedding │
      └──────────┘ └──────────┘ └──────────┘
```

**Memory types:**
- **Semantic** — stable long-term facts about the user (name, preferences, skills). Deduplicated and updated via an LLM classifier (ADD / UPDATE / REPLACE / DELETE / NOOP).
- **Bubbles (episodic)** — time-bound significant moments. Linked bidirectionally to related memories via FAISS similarity. Automatically expired after a configurable TTL.

## Config

All configuration via environment variables with prefix `EIDETIC_` or via the `configure()` function:

| Variable / kwarg | Default | Description |
|---|---|---|
| `EIDETIC_OPENAI_API_KEY` | — | OpenAI API key |
| `EIDETIC_OPENROUTER_API_KEY` | — | OpenRouter API key |
| `EIDETIC_LLM_PROVIDER` | `openai` | `openai` or `openrouter` |
| `EIDETIC_LLM_MODEL` | `gpt-4o-mini` | LLM model name |
| `EIDETIC_EMBEDDING_MODEL` | `text-embedding-3-small` | Embedding model name |
| `EIDETIC_DATABASE_URL` | `~/.eidetic/eidetic.db` | SQLAlchemy async DB URL |
| `EIDETIC_DEBUG` | `false` | Enable debug logging |
| `EIDETIC_BUBBLE_TTL_DAYS` | `30` | Bubble expiry in days (0 = never) |
| `EIDETIC_CONNECTION_THRESHOLD` | `0.6` | Min similarity for bubble links |
| `EIDETIC_MAX_CONNECTIONS_PER_BUBBLE` | `5` | Max bidirectional links per bubble |

## Development

```bash
uv sync --extra dev --extra server
uv run ruff check src/ tests/
uv run mypy src/
uv run pytest -v
```

## License

MIT
