# Read Pipeline

O read pipeline encontra fatos relevantes para uma query usando múltiplos sinais em paralelo. Toda chamada a `memory.retrieve()` executa cinco estágios: **Plan → Retrieve → Enhance → Rerank → Format**.

```mermaid
flowchart LR
    A["Query"] --> B["Plan"]
    B --> C["Retrieve\n(3 signals)"]
    C --> D["Enhance"]
    D --> E["Rerank"]
    E --> F["RetrieveResult"]
```

## Visão Geral

Quando você chama `memory.retrieve(user_id, query)`, o pipeline:

1. **Planeja o retrieval** — Um LLM planner analisa a query e decide a estratégia, reformula a query para busca semântica e identifica entidades relevantes para graph traversal.
2. **Recupera candidatos** — Três sinais paralelos (semantic, keyword, graph) encontram fatos candidatos.
3. **Enriquece resultados** — Spreading activation expande o contexto a partir de fatos semente ao longo de relacionamentos de entidades.
4. **Rerankeia** — Um LLM reranker opcional refina o ranking com base na intenção da query.
5. **Formata** — Fatos são pontuados, comprimidos dentro de um orçamento de tokens e formatados em uma string de contexto.

---

## Estágio 1: Retrieval Agent (Planner)

O retrieval agent é um planner alimentado por LLM que decide **como** recuperar, não apenas **o que** recuperar. Ele analisa a query e produz um `RetrievalPlan`.

### O Que o Planner Decide

| Campo | Descrição | Exemplo |
|-------|-----------|---------|
| `strategy` | Estratégia de retrieval | `"multi_signal"` (padrão) ou `"skip"` (para saudações) |
| `similarity_query` | Query reformulada para busca semântica | "user location city" (de "onde eu moro?") |
| `entities` | Entity keys para sinal graph | `["person:ana", "organization:acme"]` |
| `as_of_range` | Janela de time-travel (opcional) | `{"start": "2024-01-01", "end": "2024-06-30"}` |
| `broad_query` | Se deve expandir o escopo do graph | `true` para "me conte tudo sobre..." |
| `reason` | Explicação da estratégia | Para debugging e observabilidade |

### Reformulação da Query

O planner não simplesmente passa a query do usuário para busca semântica. Ele **reformula** para melhorar o matching por similaridade vetorial:

- `"onde eu moro?"` → `"user location city residence"`
- `"o que a Ana faz de trabalho?"` → `"Ana profession occupation job role"`

Isso fecha a lacuna de vocabulário entre como os usuários fazem perguntas e como os fatos são armazenados.

### Planejamento Schema-Aware

O planner inspeciona o schema real da memória para este usuário:

- Quais tipos de entidade existem (pessoas, organizações, lugares...)
- Quais entidades têm mais fatos
- Quais atributos estão armazenados

Isso ancora o plano na realidade — o planner não vai buscar entidades que não existem.

### Estratégia Skip

Para saudações e mensagens casuais ("oi", "como vai?"), o planner retorna `strategy: "skip"`, fazendo short-circuit no pipeline. Sem queries no banco, sem chamadas LLM, resposta instantânea.

!!! info "Paralelo com neurociência"
    O retrieval agent espelha **pistas de recuperação** na psicologia cognitiva. Quando você tenta lembrar algo, seu cérebro não faz uma busca exaustiva — ele usa pistas contextuais para estreitar o espaço de busca. O planner identifica entidades e reformula queries como pistas que guiam os sinais de retrieval.

---

## Estágio 2: Retrieval Multi-Signal

Três sinais independentes rodam **em paralelo** via `asyncio.gather()`, cada um encontrando candidatos de um ângulo diferente:

```mermaid
flowchart TD
    P["RetrievalPlan"] --> S["Semantic Search\n(pgvector cosine)"]
    P --> K["Keyword Search\n(SQL ILIKE)"]
    P --> G["Graph Traversal\n(BFS 2-hop)"]
    S --> M["Merge & Rank\n(RRF + weighted scoring)"]
    K --> M
    G --> M
```

### Sinal 1: Semantic Search

Usa similaridade de cosseno do pgvector para encontrar fatos cujos embeddings são próximos ao embedding da query.

- Embeds a query reformulada (do planner)
- Busca na tabela `MemoryFact` com índice HNSW
- Retorna top-N candidatos acima do threshold `min_similarity`
- Filtros: `user_id`, fatos ativos (`valid_to IS NULL`), confiança >= `min_confidence`

Este é o sinal primário — encontra fatos que são **semanticamente similares** à query, mesmo que não compartilhem keywords exatas.

### Sinal 2: Keyword Search

Matching SQL ILIKE em `fact_text` para hits exatos ou parciais de keywords.

- Extrai palavras significativas (> 2 caracteres) da query
- Faz match contra o texto do fato (até 5 keywords)
- Score = fração de palavras da query encontradas no fato

Complementa a busca semântica capturando matches exatos que a similaridade de embedding pode perder (ex: nomes próprios, termos técnicos, abreviações).

### Sinal 3: Graph Retrieval

Percorre relacionamentos de entidades para encontrar fatos conectados às entidades da query.

- Começa das entidades identificadas pelo planner
- Traversal BFS até 2 hops em `MemoryEntityRelationship`
- Fórmula de scoring: `edge_strength × recency_factor × edge_recency_factor × query_bonus`
- `query_bonus`: 1.5x quando o nome da entidade aparece no texto da query

Graph retrieval se destaca em encontrar fatos **contextuais**. Quando você pergunta sobre uma pessoa, ele também encontra fatos sobre seu local de trabalho, seus relacionamentos e seus projetos.

### Merge & Rank

Depois que os três sinais retornam, os resultados são mesclados:

1. **Deduplicar** por fact ID (o mesmo fato pode aparecer em múltiplos sinais)
2. **Aplicar decay de recência** — Decay exponencial com half-life configurável (`recency_half_life_days`, padrão 14)
3. **Aplicar decay de confiança** — Fatos mais antigos com menor confiança são penalizados
4. **Calcular score combinado** — Soma ponderada:

```python
score = (
    score_weights["semantic"]   * semantic_score +    # default 0.70
    score_weights["recency"]    * recency_score +     # default 0.20
    score_weights["importance"] * importance_score     # default 0.10
)
```

### Configuração

| Parâmetro | Default | Descrição |
|-----------|---------|-----------|
| `topk_facts` | `20` | Máximo de fatos a retornar |
| `topk_events` | `8` | Máximo de eventos a considerar |
| `min_similarity` | `0.20` | Similaridade de cosseno mínima para resultados semânticos |
| `min_confidence` | `0.55` | Confiança mínima do fato |
| `recency_half_life_days` | `14` | Half-life para decay de recência |
| `score_weights` | Veja acima | Pesos para cada sinal de scoring |
| `enable_reranker` | `True` | Se deve usar reranking LLM |

!!! info "Paralelo com neurociência"
    O retrieval multi-signal espelha **spreading activation** em redes semânticas (Collins & Loftus, 1975). Quando você pensa em "médico", a ativação se espalha para conceitos relacionados ("hospital", "remédio", "consulta") através de links associativos. Da mesma forma, graph retrieval se espalha a partir de entidades da query ao longo de arestas de relacionamento, enquanto busca semântica ativa fatos através de proximidade de embedding.

---

## Estágio 3: Enhancement

### Spreading Activation

A partir dos top-K fatos semente, o pipeline expande o contexto seguindo relacionamentos de entidades:

- Para cada fato semente, encontra os relacionamentos da entidade
- Percorre relacionamentos por N hops (`spreading_activation_hops`, padrão 2)
- Aplica fator de decay por hop (`spreading_decay_factor`, padrão 0.50)
- Retorna até M fatos adicionais por entidade

Isso captura contexto importante que não foi diretamente matcheado. Se você perguntar "o que o Rafael faz?", spreading activation pode trazer fatos sobre seu local de trabalho, time e projetos.

### Sinal de Padrão

Fatos que foram reforçados múltiplas vezes (confirmados por decisões NOOP no write) recebem um boost aditivo no score:

- Contagem alta de reforço → até 0.10 de score extra
- Captura fatos frequentemente mencionados e bem estabelecidos

### Configuração

| Parâmetro | Default | Descrição |
|-----------|---------|-----------|
| `spreading_activation_hops` | `2` | Máximo de hops a partir de fatos semente |
| `spreading_decay_factor` | `0.50` | Decay de score por hop (0.5 = dividido pela metade a cada hop) |

---

## Estágio 4: Reranking (Opcional)

Quando `enable_reranker=True`, os top candidatos são rerankeados por um LLM que considera a intenção da query:

- Respeita o significado semântico da query (não apenas overlap de keywords)
- Pode promover fatos que são indiretamente relevantes mas importantes
- Degradação graceful: se o reranker falhar (timeout, erro), o ranking original é preservado

O reranker é o estágio mais custoso, mas fornece a maior melhoria de qualidade para queries complexas.

---

## Estágio 5: Formatação

O estágio final converte fatos ranqueados em output consumível.

### Compressão de Contexto

Fatos são divididos em três tiers dentro de um orçamento de tokens (`context_max_tokens`):

| Tier | Parcela do orçamento | Conteúdo |
|------|---------------------|----------|
| **Hot** | 50% | Fatos mais relevantes (scores mais altos) |
| **Warm** | 30% | Contexto de suporte |
| **Cold** | 20% | Fatos de background (scores menores mas ainda relevantes) |

### Configuração

| Parâmetro | Default | Descrição |
|-----------|---------|-----------|
| `context_budget_tokens` | `3000` | Orçamento total de tokens para retrieval |
| `context_max_tokens` | `2000` | Máximo de tokens no contexto formatado |
| `hot_tier_ratio` | `0.50` | Parcela do orçamento para fatos top |
| `warm_tier_ratio` | `0.30` | Parcela do orçamento para fatos de suporte |

### Formato de Output

A string `context` é formatada para injeção direta em prompts LLM:

```
## Known facts about the user:
- Lives in São Paulo (confidence: 0.95)
- Works at Acme Corp as a backend engineer (confidence: 0.90)
- Wife's name is Ana (confidence: 0.92)
```

---

## RetrieveResult

```python
result = await memory.retrieve(user_id="user_123", query="...")

# Contexto pré-formatado (pronto para prompts LLM)
print(result.context)

# Fatos individuais com scores
for fact in result.facts:
    print(f"[{fact.score:.2f}] {fact.entity_name}: {fact.value}")
    print(f"  Scores: {fact.scores}")  # {"semantic": 0.85, "recency": 0.72, ...}

# Stats do pipeline
print(f"Candidates evaluated: {result.total_candidates}")
print(f"Duration: {result.duration_ms:.0f}ms")
```

---

## Diagrama do Pipeline (Completo)

```mermaid
flowchart TD
    Q["User query"] --> AG["Retrieval Agent\n(LLM planner)"]
    AG -->|skip| SKIP["Return empty\n(greeting/casual)"]
    AG -->|multi_signal| PAR["Parallel retrieval"]
    PAR --> SEM["Semantic Search\n(pgvector cosine)"]
    PAR --> KW["Keyword Search\n(SQL ILIKE)"]
    PAR --> GR["Graph Traversal\n(BFS 2-hop)"]
    SEM --> MERGE["Merge & Rank\n(dedup + weighted scoring)"]
    KW --> MERGE
    GR --> MERGE
    MERGE --> SA["Spreading Activation\n(expand context along edges)"]
    SA --> RR{"Reranker\nenabled?"}
    RR -->|yes| RERANK["LLM Rerank"]
    RR -->|no| FMT["Format & Compress"]
    RERANK --> FMT
    FMT --> RES["RetrieveResult"]
```

!!! info "Paralelo com neurociência"
    A compressão em tiers (hot/warm/cold) espelha **níveis de ativação** na working memory. No modelo de processos embutidos de Cowan, um número pequeno de itens está no foco de atenção (hot tier), cercado por memória de longo prazo ativada (warm tier), com o restante da memória de longo prazo disponível mas não ativa (cold tier). O orçamento de tokens age como o limite de capacidade da working memory.
