# Supermemory Cartesia SDK

Memory-enhanced voice agents with [Supermemory](https://supermemory.ai) and [Cartesia Line](https://cartesia.ai/agents).

## Installation

```bash
pip install supermemory-cartesia
```

## Quick Start

```python
import os
from line.llm_agent import LlmAgent, LlmConfig
from line.voice_agent_app import VoiceAgentApp
from supermemory_cartesia import SupermemoryCartesiaAgent

async def get_agent(env, call_request):
    # Extract container_tag from call metadata (typically user ID)
    container_tag = call_request.metadata.get("user_id", "default-user")

    # Create base LLM agent
    base_agent = LlmAgent(
        model="gemini/gemini-2.5-flash-preview-09-2025",
        config=LlmConfig(
            system_prompt="You are a helpful voice assistant with memory.",
            introduction="Hello! Great to talk with you again!"
        )
    )

    # Wrap with Supermemory
    memory_agent = SupermemoryCartesiaAgent(
        agent=base_agent,
        api_key=os.getenv("SUPERMEMORY_API_KEY"),
        container_tag=container_tag,
        custom_id=call_request.call_id,
    )

    return memory_agent

# Create voice agent app
app = VoiceAgentApp(get_agent=get_agent)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)
```

## Configuration

### Parameters

| Parameter       | Type         | Required | Description                                                        |
| --------------- | ------------ | -------- | ------------------------------------------------------------------ |
| `agent`         | LlmAgent     | **Yes**  | The Cartesia Line agent to wrap                                    |
| `container_tag` | str          | **Yes**  | Primary container tag for memory scoping (e.g., user ID)           |
| `custom_id`     | str          | **Yes**  | Custom ID for grouping conversation messages into a single document|
| `add_memory`    | Literal      | No       | Memory persistence mode: "always" (default) or "never"             |
| `container_tags`| List[str]    | No       | Additional container tags for organization (e.g., ["org", "prod"]) |
| `api_key`       | str          | No       | Supermemory API key (or set `SUPERMEMORY_API_KEY` env var)         |
| `config`        | MemoryConfig | No       | Advanced configuration                                             |
| `base_url`      | str          | No       | Custom API endpoint                                                |

### Advanced Configuration

```python
from supermemory_cartesia import SupermemoryCartesiaAgent

memory_agent = SupermemoryCartesiaAgent(
    agent=base_agent,
    container_tag="user-123",
    custom_id="conversation-456",
    add_memory="always",           # "always" (default) or "never"
    container_tags=["org-acme", "prod"],  # Optional: additional tags
    config=SupermemoryCartesiaAgent.MemoryConfig(
        search_limit=10,           # Max memories to retrieve
        search_threshold=0.1,      # Similarity threshold
        mode="full",               # "profile", "query", or "full"
        system_prompt="Based on previous conversations, I recall:\n\n",
    ),
)

# Read-only mode - retrieve memories but don't save new ones
read_only_agent = SupermemoryCartesiaAgent(
    agent=base_agent,
    container_tag="user-123",
    custom_id="conversation-456",
    add_memory="never",  # Only retrieve, don't save
)
```

### Memory Modes

| Mode        | Static Profile | Dynamic Profile | Search Results |
| ----------- | -------------- | --------------- | -------------- |
| `"profile"` | Yes            | Yes             | No             |
| `"query"`   | No             | No              | Yes            |
| `"full"`    | Yes            | Yes             | Yes            |

## How It Works

1. **Intercepts events** - Listens for `UserTurnEnded` events from Cartesia Line
2. **Retrieves memories** - Queries Supermemory `/v4/profile` API with user's message
3. **Enriches context** - Adds memories to event history as system message
4. **Stores messages** - Sends conversation to Supermemory (background, non-blocking)
5. **Passes to agent** - Forwards enriched event to wrapped LlmAgent

### What Gets Stored

User and assistant messages are sent to Supermemory:

```json
{
  "content": "User: What's the weather?\nAssistant: It's sunny today!",
  "container_tags": ["user-123", "org-acme", "prod"],
  "metadata": { "platform": "cartesia" }
}
```

## Architecture

Cartesia Line uses an event-driven architecture:

```
User Speaks (Audio)
    ↓
[Ink STT] → Automatic speech recognition
    ↓
UserTurnEnded Event {content: "user message", history: [...]}
    ↓
┌──────────────────────────────────────────────┐
│   SUPERMEMORY CARTESIA AGENT (Wrapper)       │
│                                              │
│  process(env, event):                        │
│    1. Intercept UserTurnEnded                │
│    2. Extract user message                   │
│    3. Query Supermemory API                  │
│    4. Enrich event.history with memories     │
│    5. Pass to wrapped LlmAgent               │
│    6. Store conversation (async background)  │
└──────────────────────────────────────────────┘
    ↓
AgentSendText Event {text: "response"}
    ↓
[Sonic TTS] → Ultra-fast speech synthesis
    ↓
Audio Output
```

## Comparison with Pipecat SDK

| Aspect                  | Pipecat                        | Cartesia Line                |
| ----------------------- | ------------------------------ | ---------------------------- |
| **Integration Pattern** | Extends `FrameProcessor`       | Wrapper around `LlmAgent`    |
| **Event Handling**      | `process_frame()` method       | `process()` method           |
| **Events**              | `LLMContextFrame`, `LLMMessagesFrame` | `UserTurnEnded`, `CallStarted` |
| **Context Object**      | `LLMContext.get_messages()`    | `event.history`              |
| **Memory Injection**    | Modify `context.add_message()` | Modify `event.history`       |

## Full Example with Tools

```python
import os
from line.llm_agent import LlmAgent, LlmConfig
from line.tools import LoopbackTool
from line.voice_agent_app import VoiceAgentApp
from supermemory_cartesia import SupermemoryCartesiaAgent

# Define custom tools
async def get_weather(location: str) -> str:
    return f"The weather in {location} is sunny, 72°F"

weather_tool = LoopbackTool(
    name="get_weather",
    description="Get current weather for a location",
    function=get_weather
)

async def get_agent(env, call_request):
    container_tag = call_request.metadata.get("user_id", "default-user")
    org_id = call_request.metadata.get("org_id")

    # Create LLM agent with tools
    base_agent = LlmAgent(
        model="gemini/gemini-2.5-flash-preview-09-2025",
        tools=[weather_tool],
        config=LlmConfig(
            system_prompt="You are a personal assistant with memory and tools.",
            introduction="Hi! How can I help you today?"
        )
    )

    # Wrap with Supermemory
    memory_agent = SupermemoryCartesiaAgent(
        agent=base_agent,
        api_key=os.getenv("SUPERMEMORY_API_KEY"),
        container_tag=container_tag,
        custom_id=call_request.call_id,
        container_tags=[org_id] if org_id else None,
        config=SupermemoryCartesiaAgent.MemoryConfig(
            mode="full",
            search_limit=15,
            search_threshold=0.15,
        )
    )

    return memory_agent

app = VoiceAgentApp(get_agent=get_agent)
```

## Development

```bash
# Clone repository
git clone https://github.com/supermemoryai/supermemory
cd supermemory/packages/cartesia-sdk-python

# Install in development mode
pip install -e ".[dev]"

# Run tests
pytest

# Format code
black .
isort .
```

## License

MIT
