# Construindo um Assistente Pessoal

Este guia mostra como dar a um agente de IA **memória igual a de um humano** usando o Arandu — pra ele lembrar o que ouviu, o que falou, quem falou o quê, e o que fez.

## O Problema

Pensa em como a tua memória funciona. Quando tu conversa com alguém:

- Tu lembra **o que a pessoa te contou** — "Pedro me disse que mora em Porto Alegre"
- Tu lembra **o que tu falou** — "Eu recomendei um restaurante pra ele"
- Tu lembra **o que aprendeu sobre outra pessoa** na conversa — "Pedro mencionou que a namorada Ana é designer"
- Tu lembra **o que tu fez** — "Eu criei um documento pro Pedro"
- Quando alguém pergunta sobre qualquer assunto, tu lembra **tudo que sabe**, independente de quem te contou ou quando

Um agente de IA não tem nada disso por padrão. Cada sessão começa em branco. O agente não sabe o que aconteceu antes, quem disse o quê, ou o que ele fez. Ele não tem memória.

O Arandu dá ao seu agente o mesmo tipo de memória que um humano tem. Cada fato carrega sobre quem é, quem disse, e quando — e a busca funciona como a lembrança humana: pergunta sobre um assunto, recebe tudo que é relevante.

## Arquitetura

Um assistente pessoal precisa de três camadas de memória:

| Camada | Ferramenta | O que guarda |
|---|---|---|
| **Memória semântica** | **Arandu** | Fatos sobre pessoas, preferências, ações, conhecimento |
| **Memória procedimental** | System prompt / config | Como o assistente deve se comportar |
| **Memória episódica** | Sistema de arquivos / banco | Logs diários, boards de tarefas, registros estruturados |

O Arandu cuida da memória semântica — a camada de "o que eu sei". Ele extrai fatos de linguagem natural, reconcilia com o conhecimento existente, e recupera por relevância.

## Padrão Principal: Gravar as Duas Pontas

O segredo: **gravar tanto as mensagens do usuário quanto as respostas do assistente**.

```python
# Usuário falou → gravar com speaker_name do usuário
await memory.write(
    agent_id="meu-assistente",
    message="Minha namorada Ana é designer, ela trabalha na Stone",
    speaker_name="Pedro",
)
# Fatos extraídos:
#   "Ana é designer" (entidade: person:ana)
#   "Ana trabalha na Stone" (entidade: person:ana, organization:stone)
#   "Ana é namorada do Pedro" (entidade: person:ana)

# Assistente respondeu → gravar com speaker_name do assistente
await memory.write(
    agent_id="meu-assistente",
    message="Criei a nota 'Ana.md' no vault e agendei lembrete de aniversário pro dia 15/05",
    speaker_name="Assistente",
)
# Fatos extraídos:
#   "Assistente criou nota Ana.md no vault" (entidade: person:assistente)
#   "Assistente agendou lembrete de aniversário pro dia 15/05" (entidade: person:assistente)
```

O `speaker_name` resolve pronomes: "eu criei" com `speaker_name="Assistente"` vira "Assistente criou". Cada fato carrega o metadado `speaker` dizendo quem falou.

## Fluxo de Sessão

```
┌─────────────────────────────────────────────────────┐
│ Início da sessão                                     │
│                                                      │
│  1. retrieve(query="contexto recente")               │
│     → Recupera o que o assistente sabe e fez         │
│     → Injeta no system prompt como contexto          │
│                                                      │
├─────────────────────────────────────────────────────┤
│ Durante a conversa                                   │
│                                                      │
│  2. Usuário manda mensagem                           │
│     → write(message=msg, speaker_name="Pedro")       │
│                                                      │
│  3. Assistente processa e responde                   │
│     → retrieve(query=msg) pra buscar contexto        │
│     → Gera resposta                                  │
│     → write(message=resposta, speaker_name="Assist") │
│                                                      │
├─────────────────────────────────────────────────────┤
│ Fim da sessão                                        │
│                                                      │
│  Nada especial. Tudo já foi gravado durante          │
│  a conversa. A próxima sessão começa com retrieve.   │
└─────────────────────────────────────────────────────┘
```

## Exemplos de Retrieval

Uma vez que as duas pontas estão gravadas, o assistente consegue responder perguntas sobre qualquer pessoa e assunto:

```python
# "O que eu sei sobre a Ana?"
result = await memory.retrieve(agent_id="meu-assistente", query="Ana")
# Retorna TODOS os fatos sobre Ana, independente de quem falou:
#   - Ana é designer (Pedro contou)
#   - Ana trabalha na Stone (Pedro contou)
#   - Assistente criou nota Ana.md (Assistente fez)

# "O que eu fiz recentemente?"
result = await memory.retrieve(agent_id="meu-assistente", query="o que foi feito recentemente")
# Retorna ações do assistente:
#   - Criou nota Ana.md
#   - Agendou lembrete de aniversário

# Restrito a uma entidade específica
result = await memory.retrieve(
    agent_id="meu-assistente",
    query="trabalho",
    entity_keys=["person:ana"],
)
# Retorna só fatos sobre trabalho relacionados à Ana
```

`entity_keys` aceita aliases também — passa `"person:ana"` mesmo que a canônica seja `person:ana_silva`, que o SDK resolve via `memory_entity_aliases`. Chaves que não resolvem aparecem em `result.warnings` pro caller distinguir "sem matches" de "chave errada":

```python
result = await memory.retrieve(
    agent_id="meu-assistente",
    query="trabalho",
    entity_keys=["person:ana", "person:unknown"],
)
# result.warnings → ["entity_key 'person:unknown' not found (not canonical, no matching alias)"]
```

## Provenance do Speaker

Todo fato carrega o campo `speaker` dizendo quem falou a mensagem de onde foi extraído. Disponível tanto no `FactDetail` (via `get`/`get_all`) quanto no `ScoredFact` (via `retrieve`):

```python
result = await memory.retrieve(agent_id="meu-assistente", query="Stone")
for fact in result.facts:
    print(f"[{fact.speaker}] {fact.fact_text}")
# Output:
#   [Pedro] Ana trabalha na Stone
#   [Assistente] Stone é uma fintech brasileira, fundada em 2012
```

Nenhum concorrente persiste provenance do speaker nativamente — isso é um diferencial do Arandu.

## Gerenciando a Memória

O assistente pode inspecionar e gerenciar sua própria memória:

```python
# Listar entidades conhecidas
entities = await memory.entities(agent_id="meu-assistente")
for e in entities:
    print(f"[{e.entity_type}] {e.display_name} ({e.fact_count} fatos)")
# [person] Pedro (12 fatos)
# [person] Ana (5 fatos)
# [organization] Stone (3 fatos)
# [person] Assistente (8 fatos)

# Listar fatos de uma entidade específica
ana_facts = await memory.get_all(
    agent_id="meu-assistente",
    entity_keys=["person:ana"],
)

# Deletar um fato específico
await memory.delete(agent_id="meu-assistente", fact_id="algum-uuid")

# Resetar toda a memória (use com cuidado)
await memory.delete_all(agent_id="meu-assistente")
```

## Exemplo Completo

Um assistente pessoal mínimo com memória:

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


async def handle_message(
    memory: MemoryClient,
    agent_id: str,
    user_msg: str,
    speaker: str,
) -> str:
    # 1. Gravar mensagem do usuário
    await memory.write(
        agent_id=agent_id,
        message=user_msg,
        speaker_name=speaker,
    )

    # 2. Recuperar contexto relevante
    context = await memory.retrieve(agent_id=agent_id, query=user_msg)

    # 3. Gerar resposta (substitua pela sua chamada LLM)
    llm = memory._llm  # reutiliza o provider do SDK
    response = await llm.complete(
        messages=[
            {
                "role": "system",
                "content": f"Você é um assistente pessoal. Contexto da memória:\n{context.context}",
            },
            {"role": "user", "content": user_msg},
        ]
    )

    # 4. Gravar resposta do assistente
    await memory.write(
        agent_id=agent_id,
        message=response.text,
        speaker_name="Assistente",
    )

    return response.text


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()

    agent_id = "assistente-pedro"

    try:
        # Início da sessão: verificar o que aconteceu antes
        recent = await memory.retrieve(agent_id=agent_id, query="contexto recente")
        if recent.facts:
            print("Retomando com contexto de sessões anteriores...")

        # Simular conversa
        reply = await handle_message(
            memory, agent_id,
            "Minha namorada Ana é designer na Stone",
            speaker="Pedro",
        )
        print(f"Assistente: {reply}")

        reply = await handle_message(
            memory, agent_id,
            "O que você sabe sobre a Ana?",
            speaker="Pedro",
        )
        print(f"Assistente: {reply}")
    finally:
        await memory.close()


asyncio.run(main())
```

## Como o Arandu se Compara

| Capacidade | Mem0 | Zep | Letta | Arandu |
|---|---|---|---|---|
| Extração de fatos do texto | Sim | Sim | Não (manual) | Sim |
| Filtro no retrieve | `user_id`/`agent_id` | `group_ids` | Labels de bloco | `entity_keys` |
| Provenance do speaker | Não | Indireta (lookup no episódio) | Não | **Sim (nativo)** |
| Grafo de entidades | Opcional | Sim | Não | Sim |
| Operações CRUD | Sim | Sim | Sim | Sim |
| Reconciliação (dedup) | Sim | Automática | Manual | Sim (via LLM) |
