# Memory

KOAN provides three memory systems: persistent thread storage, semantic vector memory, and forkable in-memory threads with time travel.

## Install

```bash
pip install koan[memory]         # Thread store (SQLAlchemy + aiosqlite)
pip install koan[vector]         # Vector memory (ChromaDB)
```

## Thread Store

Persistent conversation history backed by SQLAlchemy async (SQLite by default):

```python
from koan.memory import ThreadStore

store = ThreadStore("sqlite+aiosqlite:///memory.db")
await store.init()  # Creates tables

# Append messages
await store.append("thread-1", role="user", content="Hello!")
await store.append("thread-1", role="assistant", content="Hi there!")
await store.append("thread-1", role="user", content="What is KOAN?")

# Retrieve messages (ordered by time)
messages = await store.get_messages("thread-1")
for msg in messages:
    print(f"[{msg.role}] {msg.content}")

# List threads
threads = await store.list_threads()
# Filter by user
threads = await store.list_threads(user_id="alice")

# Delete a thread
deleted_count = await store.delete_thread("thread-1")

# Clean up
await store.close()
```

### In-Memory Store

For testing or ephemeral use, omit the database URL:

```python
store = ThreadStore()  # In-memory SQLite
await store.init()
```

### User Scoping

Messages can be tagged with a `user_id` for multi-tenant applications:

```python
await store.append("thread-1", role="user", content="Hello", user_id="alice")
threads = await store.list_threads(user_id="alice")
```

## Vector Memory

Semantic vector memory using ChromaDB for storage and retrieval:

```python
from koan.memory import VectorMemory

memory = VectorMemory(collection_name="agent_memory")

# Store memories
await memory.store("User prefers dark mode", user_id="alice")
await memory.store("User is a Python developer", user_id="alice")
await memory.store("User lives in Tokyo", user_id="alice")

# Retrieve by semantic similarity
results = await memory.retrieve("What programming language?", user_id="alice", n_results=3)
for record in results:
    print(record.content)  # "User is a Python developer" ranked first

# User scoping -- alice can't see bob's memories
await memory.store("User likes Java", user_id="bob")
results = await memory.retrieve("programming", user_id="alice")
# Only returns alice's records

# Count and delete
count = await memory.count()
await memory.delete(record_id="some-id")
```

## Forkable Threads (Time Travel)

In-memory conversation threads with checkpoint, rewind, and fork capabilities:

```python
from koan.memory import ForkableThread

thread = ForkableThread(thread_id="conversation-1")

# Build a conversation
thread.append(role="user", content="Hello")
thread.append(role="assistant", content="Hi! How can I help?")

# Save a checkpoint
thread.checkpoint("greeting_done")

thread.append(role="user", content="Tell me about KOAN")
thread.append(role="assistant", content="KOAN is a framework for...")

print(len(thread))  # 4 messages
```

### Rewind (Time Travel)

Roll back to any checkpoint, discarding messages after that point:

```python
# Go back in time
thread.rewind_to("greeting_done")

print(len(thread))  # 2 messages -- "Tell me about KOAN" and response are gone

# Continue from the checkpoint with a different path
thread.append(role="user", content="What's the weather?")
thread.append(role="assistant", content="It's sunny!")
```

### Fork (Branching)

Create an independent copy of the conversation at a checkpoint:

```python
thread = ForkableThread(thread_id="main")
thread.append(role="user", content="Hello")
thread.checkpoint("start")
thread.append(role="assistant", content="Response A")

# Fork from checkpoint -- creates an independent branch
branch = thread.fork_at("start")
branch.append(role="assistant", content="Response B -- different approach")

# Original is unchanged
print(len(thread))   # 2 (Hello + Response A)
print(len(branch))   # 2 (Hello + Response B)

# Fork from current position (no checkpoint needed)
snapshot = thread.fork()
```

### Use Cases

- **A/B testing responses**: Fork at a decision point, try different agent strategies
- **Error recovery**: Checkpoint before risky operations, rewind on failure
- **What-if analysis**: Fork to explore alternative conversation paths
- **Debugging**: Rewind to the point where an agent went wrong

### Example: Agent with Retry

```python
thread = ForkableThread(thread_id="session")
thread.append(role="user", content="Complex question...")
thread.checkpoint("before_attempt")

# First attempt
result = await agent.run("Complex question...")
thread.append(role="assistant", content=result.output)

if result.is_error:
    # Rewind and try a different approach
    thread.rewind_to("before_attempt")
    result = await fallback_agent.run("Complex question...")
    thread.append(role="assistant", content=result.output)
```
