# Zymemory Python SDK

**Official Python client for the Zymemory API** - Long-term memory for AI applications.

Zymemory provides a powerful memory system for AI assistants, chatbots, and agents, enabling them to remember and recall information across conversations.

[![PyPI version](https://badge.fury.io/py/zymemory.svg)](https://pypi.org/project/zymemory/)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![License: Proprietary](https://img.shields.io/badge/License-Proprietary-red.svg)](LICENSE)

## Features

- **Semantic Memory Search** - Find relevant memories using natural language
- **Memory Linking** - Connect related memories for graph-based recall
- **Conversation Storage** - Automatically cluster conversations into structured memories
- **Smart Clustering** - AI-powered organization of information
- **Multi-tenant** - Secure isolation for different users and organizations
- **Fast & Scalable** - Built on high-performance infrastructure

## Installation

```bash
pip install zymemory
```

## Quick Start

```python
from zymemory import ZymemoryClient

# Initialize client
client = ZymemoryClient(
    api_key="your-org-api-key",
    org_email="your-org@example.com",
    user_token="user-specific-token"
)

# Search for memories
results = client.search("What are my coffee preferences?")
for memory in results.memories:
    print(f"{memory.content}")
    print(f"   Keywords: {', '.join(memory.keywords)}")

# Create a new memory
memory = client.create_memory("I prefer oat milk lattes in the morning")
print(f"Created memory #{memory.id}")

# Store a conversation
client.store_conversation(
    user_input="What's the weather today?",
    assistant_output="It's sunny and 72°F!"
)
```

## Authentication

Zymemory uses a two-tier authentication system:

1. **Organization API Key** - Identifies your organization
2. **User Token** - Identifies the end user

### Getting Started

1. **Get your organization API key** from the Zymemory dashboard
2. **Register your end users:**

```python
# First, initialize with just org credentials
client = ZymemoryClient(
    api_key="your-org-key",
    org_email="your-org@example.com"
)

# Register a new end user
user = client.register_user(
    external_id="user_123",  # Your system's user ID
    name="John Doe",
    email="john@example.com"
)

# Save the user token for future requests
print(f"User token: {user.token}")

# Now use it for all memory operations
client.user_token = user.token
```

## Usage Examples

### Memory Management

```python
from zymemory import ZymemoryClient

client = ZymemoryClient(
    api_key="your-key",
    org_email="org@example.com",
    user_token="user-token"
)

# Create memories
memory1 = client.create_memory("I love dark roast coffee")
memory2 = client.create_memory("My favorite cafe is Blue Bottle", auto_merge=True)

# List all memories with pagination
result = client.list_memories(page=1, page_size=20)
print(f"Total memories: {result.total}")
for memory in result.memories:
    print(f"  {memory.id}: {memory.content}")

# Delete a memory
client.delete_memory(memory1.id)
```

### Searching Memories

```python
# Simple search
results = client.search("coffee preferences", top_k=5)

# Process results
print(f"Found {len(results.memories)} memories:")
for memory in results.memories:
    print(f"  {memory.content}")
    print(f"     Keywords: {memory.keywords}")
    print(f"     Connections: {memory.link_count}")

# Get just the memories (exclude unassigned conversations)
memories = client.search_memories_only("favorite foods", top_k=10)
```

### Memory Linking

```python
# Create links between related memories
client.create_link(source_id=1, destination_id=2)

# Get connected memories
links = client.get_memory_radius(memory_id=1, depth=2)
print(f"Outgoing links: {len(links['outgoing'])}")
print(f"Incoming links: {len(links['incoming'])}")

# Explore the memory graph
for link in links['outgoing']:
    dest = link['destination']
    print(f"  → {dest['content']}")
```

### Conversation Storage

```python
# Store conversation turns
# The backend automatically processes these into structured memories
client.store_conversation(
    user_input="I want to learn Python",
    assistant_output="Great! Python is perfect for beginners..."
)

client.store_conversation(
    user_input="What's a good first project?",
    assistant_output="Try building a to-do list app..."
)

# Later, search across conversations
results = client.search("learning programming")
# The system will have clustered related conversations into memories
```

### Building a Chatbot with Memory

```python
from zymemory import ZymemoryClient
from anthropic import Anthropic  # or openai, etc.

class MemoryChatbot:
    def __init__(self, zymemory_client, llm_client):
        self.memory = zymemory_client
        self.llm = llm_client

    def chat(self, user_message):
        # 1. Search for relevant memories
        context = self.memory.search(user_message, top_k=5)

        # 2. Build prompt with memory context
        memory_context = "\\n".join([
            f"- {m.content}" for m in context.memories[:3]
        ])

        system_prompt = f"""You are a helpful assistant with long-term memory.

Here's what you remember:
{memory_context}

Use this information to provide personalized responses."""

        # 3. Get LLM response
        response = self.llm.messages.create(
            model="claude-3-haiku-20240307",
            system=system_prompt,
            messages=[{"role": "user", "content": user_message}]
        )

        assistant_message = response.content[0].text

        # 4. Store conversation for future recall
        self.memory.store_conversation(user_message, assistant_message)

        return assistant_message

# Use it
memory_client = ZymemoryClient(api_key="...", org_email="...", user_token="...")
llm = Anthropic(api_key="...")
bot = MemoryChatbot(memory_client, llm)

print(bot.chat("What did I say about coffee?"))
```

### Bulk Operations

```python
# Get all memories (useful for export)
all_memories = client.get_all_memories(max_pages=10)
print(f"Total: {len(all_memories)} memories")

# Export to JSON
import json
with open("memories_backup.json", "w") as f:
    json.dump([{
        "id": m.id,
        "content": m.content,
        "keywords": m.keywords
    } for m in all_memories], f, indent=2)
```

## API Reference

### `ZymemoryClient`

#### Constructor

```python
ZymemoryClient(
    api_key: str,
    org_email: str,
    user_token: Optional[str] = None,
    api_url: str = "https://api.heymaple.app",
    timeout: int = 30
)
```

#### Methods

##### Memory Management
- `create_memory(content: str, auto_merge: bool = False) -> Memory`
- `delete_memory(memory_id: int) -> dict`
- `list_memories(page: int = 1, page_size: int = 20) -> MemoryList`

##### Search & Retrieval
- `search(query: str, top_k: int = 5) -> SearchResult`
- `search_memories_only(query: str, top_k: int = 5) -> List[Memory]`

##### Memory Links
- `create_link(source_id: int, destination_id: int) -> dict`
- `get_memory_radius(memory_id: int, depth: int = 1) -> dict`

##### Conversations
- `store_conversation(user_input: str, assistant_output: str) -> dict`

##### User Management
- `register_user(external_id: str, name: str, email: Optional[str] = None) -> User`

##### Utilities
- `get_all_memories(max_pages: int = 10, page_size: int = 100) -> List[Memory]`

### Models

#### `Memory`
```python
@dataclass
class Memory:
    id: int
    content: str
    keywords: List[str]
    conversations: List[tuple[str, str]]
    created_at: Optional[str]
    link_count: Optional[int]
    metadata: Optional[Dict[str, Any]]
```

#### `SearchResult`
```python
@dataclass
class SearchResult:
    memories: List[Memory]
    unassigned: List[Dict[str, Any]]
```

#### `MemoryList`
```python
@dataclass
class MemoryList:
    memories: List[Memory]
    total: int
    page: int
    page_size: int
```

## Error Handling

```python
from zymemory import ZymemoryClient, ZymemoryError, AuthenticationError, APIError

client = ZymemoryClient(api_key="...", org_email="...", user_token="...")

try:
    memory = client.create_memory("Important information")
except AuthenticationError as e:
    print(f"Authentication failed: {e}")
except APIError as e:
    print(f"API error {e.status_code}: {e}")
except ZymemoryError as e:
    print(f"Zymemory error: {e}")
```

### Exception Hierarchy

- `ZymemoryError` - Base exception
  - `AuthenticationError` - Invalid credentials
  - `APIError` - API returned error response
  - `ValidationError` - Invalid request data
  - `NetworkError` - Connection/timeout issues
  - `RateLimitError` - Rate limit exceeded

## Advanced Features

### Custom API URL (Self-hosted)

```python
client = ZymemoryClient(
    api_key="your-key",
    org_email="org@example.com",
    user_token="user-token",
    api_url="https://your-self-hosted-instance.com"
)
```

### Per-Request User Override

```python
# Use different user token for specific requests
memory = client.create_memory("content", user_token="different-user-token")
```

### Request Timeout

```python
client = ZymemoryClient(
    api_key="your-key",
    org_email="org@example.com",
    timeout=60  # 60 seconds
)
```

## Development

### Running Tests

```bash
cd zymemory
pip install -e ".[dev]"
pytest tests/
```

### Type Checking

```bash
mypy zymemory/
```

### Code Formatting

```bash
black zymemory/
```

## Support

- **Documentation**: https://heymaple.app/docs
- **Issues**: https://github.com/ranjalii/zymemory/issues
- **Email**: contact@heymaple.app

## License

This software is proprietary and confidential. All rights reserved by Hey Maple.

**Redistribution is strictly prohibited.** This software may only be used for internal business purposes. You may not redistribute, sublicense, sell, transfer, or make this software available to third parties in any form.

For licensing inquiries, contact: contact@heymaple.app

See LICENSE file for complete terms.

## Changelog

### v0.1.0 (2024-03-18)
- Initial release
- Core memory management
- Search and retrieval
- Memory linking
- Conversation storage
- User registration

---

Built with by Hey Maple
