# Cookbook

Complete, copy-paste-ready examples for common use cases.

---

## Basic Usage

The simplest integration: write facts from user messages and retrieve context for responses.

```python
import asyncio
from arandu import MemoryClient
from arandu.providers.openai import OpenAIProvider


async def main():
    provider = OpenAIProvider(api_key="sk-...")
    memory = MemoryClient(
        database_url="postgresql+psycopg://memory:memory@localhost:5432/memory",
        llm=provider,
        embeddings=provider,
    )
    await memory.initialize()

    try:
        # Simulate a conversation
        messages = [
            "Hi, I'm Rafael. I'm a backend engineer at Acme Corp in São Paulo.",
            "My girlfriend Ana is a UX designer. We have a cat named Pixel.",
            "I've been learning Rust lately, mostly on weekends.",
            "Actually, I just moved to Rio de Janeiro. Still remote at Acme.",
        ]

        for msg in messages:
            result = await memory.write(agent_id="rafael", message=msg, speaker_name="Rafael")
            added = len(result.facts_added)
            updated = len(result.facts_updated)
            print(f"Write: +{added} facts, ~{updated} updates ({result.duration_ms:.0f}ms)")

        # Retrieve context for different queries
        queries = [
            "where does Rafael live?",
            "tell me about Rafael's relationships",
            "what are Rafael's hobbies?",
        ]

        for query in queries:
            result = await memory.retrieve(agent_id="rafael", query=query)
            print(f"\nQuery: {query}")
            print(f"Found {len(result.facts)} facts ({result.duration_ms:.0f}ms)")
            for fact in result.facts[:5]:
                print(f"  [{fact.score:.2f}] {fact.entity_name}: {fact.fact_text}")
    finally:
        await memory.close()


asyncio.run(main())
```

---

## Using Anthropic (Claude)

Use Claude as your LLM while keeping OpenAI for embeddings (Anthropic doesn't offer an embeddings API):

```python
import asyncio
from arandu import MemoryClient, MemoryConfig
from arandu.providers.anthropic import AnthropicProvider
from arandu.providers.openai import OpenAIProvider


async def main():
    # Claude for reasoning, OpenAI for embeddings
    llm = AnthropicProvider(api_key="sk-ant-...", model="claude-sonnet-4-20250514")
    embeddings = OpenAIProvider(api_key="sk-...")

    memory = MemoryClient(
        database_url="postgresql+psycopg://memory:memory@localhost/memory",
        llm=llm,
        embeddings=embeddings,
    )
    await memory.initialize()

    try:
        result = await memory.write(
            agent_id="demo",
            message="I love hiking in the mountains. Last weekend I went to Serra da Mantiqueira.",
            speaker_name="Rafael",
        )
        print(f"Extracted {len(result.facts_added)} facts using Claude")

        context = await memory.retrieve(agent_id="demo", query="outdoor activities")
        print(context.context)
    finally:
        await memory.close()


asyncio.run(main())
```

---

## Using Other LLM Providers

`OpenAIProvider` works with any provider that exposes an OpenAI-compatible API. Just change `api_key`, `model`, and `base_url`:

```python
from arandu import MemoryClient
from arandu.providers.openai import OpenAIProvider

# DeepSeek V3 (cheap, high quality)
llm = OpenAIProvider(
    api_key="sk-deepseek-...",
    model="deepseek-chat",
    base_url="https://api.deepseek.com/v1",
)

# Groq (fast inference)
llm = OpenAIProvider(
    api_key="gsk_...",
    model="llama-3.3-70b-versatile",
    base_url="https://api.groq.com/openai/v1",
)

# Together AI
llm = OpenAIProvider(
    api_key="tog_...",
    model="meta-llama/Llama-3.3-70B-Instruct-Turbo",
    base_url="https://api.together.xyz/v1",
)

# Fireworks AI
llm = OpenAIProvider(
    api_key="fw_...",
    model="accounts/fireworks/models/llama-v3p3-70b-instruct",
    base_url="https://api.fireworks.ai/inference/v1",
)

# Local model via Ollama
llm = OpenAIProvider(
    api_key="ollama",  # any non-empty string
    model="llama3.1",
    base_url="http://localhost:11434/v1",
)

# Use with Arandu (same as OpenAI)
memory = MemoryClient(
    database_url="postgresql+psycopg://...",
    llm=llm,
    embeddings=OpenAIProvider(api_key="sk-..."),  # embeddings still need OpenAI
)
```

!!! note "Embeddings still require OpenAI (or a custom provider)"
    Most OpenAI-compatible providers don't offer embedding APIs. Use `OpenAIProvider` with your real OpenAI key for embeddings, or implement a custom `EmbeddingProvider` (see [Custom Providers](custom-providers.md)).

---

## Advanced Configuration (Retrieval Tuning)

Fine-tune retrieval for different use cases:

```python
import asyncio
from arandu import MemoryClient, MemoryConfig
from arandu.providers.openai import OpenAIProvider


async def main():
    # Single provider for all LLM operations (extraction, reranker, etc.)
    llm = OpenAIProvider(api_key="sk-...", model="gpt-4o")

    # Configuration for a chatbot that needs broad, fresh context
    config = MemoryConfig(
        # Extraction: tight timeout for real-time chat
        extraction_timeout_sec=15.0,

        # Retrieval: more results, favor recency
        topk_facts=40,
        min_similarity=0.15,          # cast a wider net
        recency_half_life_days=7,     # favor recent facts more aggressively

        # Score weights: boost recency for a fast-moving conversation
        score_weights={
            "semantic": 0.50,
            "recency": 0.35,
            "importance": 0.15,
        },

        # Reranker
        enable_reranker=True,

        # Context: larger budget for rich responses
        context_max_tokens=3000,

        # Spreading activation: wider context expansion
        spreading_activation_hops=3,
        spreading_max_related_entities=8,

        # Timezone for recency calculations
        timezone="America/Sao_Paulo",
    )

    memory = MemoryClient(
        database_url="postgresql+psycopg://memory:memory@localhost/memory",
        llm=llm,
        embeddings=llm,
        config=config,
    )
    await memory.initialize()

    try:
        # Write a series of messages
        await memory.write(agent_id="demo", message="I started a new job at TechCorp today!", speaker_name="Rafael")
        await memory.write(agent_id="demo", message="My manager's name is Sarah. She seems great.", speaker_name="Rafael")
        await memory.write(agent_id="demo", message="The office is in downtown with a nice view.", speaker_name="Rafael")

        # Retrieve with tuned settings
        result = await memory.retrieve(agent_id="demo", query="what's new with the user?")
        print(f"Retrieved {len(result.facts)} facts")
        print(f"Context ({len(result.context)} chars):")
        print(result.context)

        # Check individual scores to verify tuning
        for fact in result.facts:
            print(f"\n  [{fact.score:.3f}] {fact.fact_text}")
            print(f"    Scores: {fact.scores}")
    finally:
        await memory.close()


asyncio.run(main())
```

---

## Background Jobs Integration

Set up periodic maintenance to keep memory organized:

```python
import asyncio
from arandu import (
    MemoryClient,
    MemoryConfig,
    cluster_user_facts,
    compute_entity_importance,
    detect_communities,
    refresh_entity_summaries,
    run_consolidation,
    run_memify,
)
from arandu.providers.openai import OpenAIProvider
from arandu.db import create_engine, create_session_factory


async def run_maintenance(
    database_url: str,
    agent_ids: list[str],
    provider: OpenAIProvider,
    config: MemoryConfig,
) -> None:
    """Run all background maintenance jobs for a list of users."""
    engine = create_engine(database_url)
    session_factory = create_session_factory(engine)

    try:
        async with session_factory() as session:
            for agent_id in agent_ids:
                print(f"\n--- Maintenance for {agent_id} ---")

                # 1. Importance scoring (cheap, SQL-only)
                importance = await compute_entity_importance(session, agent_id, config)
                print(f"  Importance: scored {importance.entities_scored} entities")

                # 2. Summary refresh (moderate, LLM)
                summaries = await refresh_entity_summaries(
                    session, agent_id, provider, config
                )
                print(f"  Summaries: refreshed {summaries.summaries_refreshed}")

                # 3. Clustering (moderate, LLM)
                clusters = await cluster_user_facts(
                    session, agent_id, provider, provider, config
                )
                print(f"  Clustering: {clusters.clusters_created} clusters")

                # 4. Community detection
                communities = await detect_communities(
                    session, agent_id, provider, provider, config
                )
                print(f"  Communities: {communities.communities_created} created")

                # 5. Consolidation (moderate, LLM)
                consolidation = await run_consolidation(session, agent_id, provider, config)
                print(f"  Consolidation: {consolidation.observations_created} observations")

                # 6. Memify (moderate, LLM)
                memify = await run_memify(session, agent_id, provider, provider, config)
                print(f"  Memify: {memify.facts_scored} facts scored")

            await session.commit()
    finally:
        await engine.dispose()


async def main():
    provider = OpenAIProvider(api_key="sk-...")
    config = MemoryConfig()
    database_url = "postgresql+psycopg://memory:memory@localhost/memory"

    # Run once
    await run_maintenance(database_url, ["user_123", "user_456"], provider, config)

    # Or schedule with asyncio
    # while True:
    #     await run_maintenance(database_url, agent_ids, provider, config)
    #     await asyncio.sleep(4 * 3600)  # every 4 hours


asyncio.run(main())
```

---

## Multi-Agent Setup

Handle multiple agents with isolated memory spaces:

```python
import asyncio
from arandu import MemoryClient
from arandu.providers.openai import OpenAIProvider


async def main():
    provider = OpenAIProvider(api_key="sk-...")
    memory = MemoryClient(
        database_url="postgresql+psycopg://memory:memory@localhost/memory",
        llm=provider,
        embeddings=provider,
    )
    await memory.initialize()

    try:
        # Each agent has completely isolated memory
        await memory.write(
            agent_id="alice",
            message="I work at Google as a PM. I live in Mountain View.",
            speaker_name="Alice",
        )
        await memory.write(
            agent_id="bob",
            message="I'm a freelance designer based in Berlin.",
            speaker_name="Bob",
        )

        # Agent alice's context only shows alice's facts
        alice_ctx = await memory.retrieve(agent_id="alice", query="where do they work?")
        print("Alice:", alice_ctx.context)

        # Agent bob's context only shows bob's facts
        bob_ctx = await memory.retrieve(agent_id="bob", query="where do they work?")
        print("Bob:", bob_ctx.context)
    finally:
        await memory.close()


asyncio.run(main())
```

---

## Multi-Speaker (Therapy Session)

Two speakers writing to the same agent with the same session - pronouns like "Eu" resolve to the correct speaker each time.

```python
import asyncio
from arandu import MemoryClient
from arandu.providers.openai import OpenAIProvider


async def main():
    provider = OpenAIProvider(api_key="sk-...")
    memory = MemoryClient(
        database_url="postgresql+psycopg://memory:memory@localhost/memory",
        llm=provider,
        embeddings=provider,
    )
    await memory.initialize()

    try:
        # Two speakers, same agent, same session
        await memory.write(
            agent_id="therapy_bot",
            message="Eu me sinto ignorada pelo Carlos. Ele nunca me ouve.",
            speaker_name="Ana Silva",
            session_id="sessao_001",
        )
        await memory.write(
            agent_id="therapy_bot",
            message="Eu trabalho 12 horas por dia pra sustentar a família.",
            speaker_name="Carlos Silva",
            session_id="sessao_001",
        )

        # Retrieve — facts attributed to correct speakers
        result = await memory.retrieve(agent_id="therapy_bot", query="Como a Ana se sente?")
        # Returns: "Ana Silva feels ignored by Carlos" (not Carlos's facts)
        for fact in result.facts:
            print(f"  [{fact.score:.2f}] {fact.entity_name}: {fact.fact_text}")
    finally:
        await memory.close()


asyncio.run(main())
```

---

## Multi-Session (Work + Personal)

Same speaker, same agent, different session_ids. Retrieve without session_id searches all sessions.

```python
import asyncio
from arandu import MemoryClient
from arandu.providers.openai import OpenAIProvider


async def main():
    provider = OpenAIProvider(api_key="sk-...")
    memory = MemoryClient(
        database_url="postgresql+psycopg://memory:memory@localhost/memory",
        llm=provider,
        embeddings=provider,
    )
    await memory.initialize()

    try:
        # Work context
        await memory.write(
            agent_id="assistant",
            message="Preciso entregar o relatório até sexta.",
            speaker_name="Marcos",
            session_id="work",
        )

        # Personal context
        await memory.write(
            agent_id="assistant",
            message="Minha mãe tá doente, vou visitar ela no fim de semana.",
            speaker_name="Marcos",
            session_id="personal",
        )

        # Retrieve searches ALL sessions by default
        result = await memory.retrieve(agent_id="assistant", query="O que o Marcos precisa fazer?")
        # Returns facts from BOTH work and personal sessions
        for fact in result.facts:
            print(f"  [{fact.score:.2f}] {fact.entity_name}: {fact.fact_text}")
    finally:
        await memory.close()


asyncio.run(main())
```

---

## Customer Service Bot (Multiple Customers)

Same agent serving different customers - each customer's facts are isolated by speaker_name.

```python
import asyncio
from arandu import MemoryClient
from arandu.providers.openai import OpenAIProvider


async def main():
    provider = OpenAIProvider(api_key="sk-...")
    memory = MemoryClient(
        database_url="postgresql+psycopg://memory:memory@localhost/memory",
        llm=provider,
        embeddings=provider,
    )
    await memory.initialize()

    try:
        # Customer 1
        await memory.write(
            agent_id="support_bot",
            message="Meu pedido #1234 não chegou.",
            speaker_name="Maria Oliveira",
        )

        # Customer 2
        await memory.write(
            agent_id="support_bot",
            message="Quero trocar o produto que comprei ontem.",
            speaker_name="João Santos",
        )

        # Retrieve for specific customer — agent remembers each one separately
        result = await memory.retrieve(agent_id="support_bot", query="Qual o problema da Maria?")
        for fact in result.facts:
            print(f"  [{fact.score:.2f}] {fact.entity_name}: {fact.fact_text}")
    finally:
        await memory.close()


asyncio.run(main())
```
