# Read Pipeline

Quando você chama `memory.retrieve()`, o SDK busca tudo que sabe sobre um agente e retorna os fatos mais relevantes pra sua query - ranqueados, pontuados e formatados numa string que você pode colar direto num prompt de LLM.

**Você não precisa entender os internals pra usar.** Basta chamar `retrieve()` e usar `result.context`. Esta página explica o que acontece por baixo dos panos, pra quando você quiser ajustar o comportamento ou debugar resultados.

```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

Toda chamada a `memory.retrieve(agent_id, query)` executa cinco estágios:

1. **Plan** - Descobre *o que* buscar. Detecta saudações, padrões de agregação e queries amplas. Entity resolution roda deterministicamente em paralelo.
2. **Retrieve** - Busca fatos usando três métodos em paralelo: similaridade de significado, match de palavras-chave e travessia do grafo de relacionamentos.
3. **Enhance** - Expande o contexto seguindo relacionamentos entre entidades pra encontrar fatos relacionados que não foram diretamente matcheados.
4. **Rerank** - Um LLM reavalia os top resultados e reordena pela relevância real pra sua query.
5. **Format** - Comprime os fatos ranqueados numa string com orçamento de tokens, organizada por tiers de relevância.

---

## Estágio 1: Planner Determinístico

**Em português claro:** Antes de buscar, o pipeline analisa sua query pra decidir que tipo de busca rodar. Detecta saudações (skip), queries de agregação ("quem são meus amigos?"), requests amplas ("me conta tudo") e identifica quais entidades são mencionadas. Tudo **determinístico** — mesma query sempre produz o mesmo plano, zero chamadas LLM.

O planner produz um `RetrievalPlan` usando regex pattern matching e lookups no schema. Nenhum LLM é envolvido.

!!! note "Por que sem LLM no planner?"
    Antes da v0.13.0, o planner chamava um LLM pra reformulação de query e extração de entidades. Isso introduzia **não-determinismo**: a mesma query contra a mesma memória podia retornar fatos diferentes entre runs, porque APIs cloud de LLM não são determinísticas mesmo com `temperature=0` (comportamento documentado — batching, fp16 rounding, roteamento de GPU introduzem variação). A v0.13.0 substitui o planner LLM por uma implementação totalmente determinística. A query vai direto pra busca semântica sem alteração, e extração de entidades é feita por um resolver determinístico dedicado.

### 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 para busca semântica (sempre a original) | `"onde eu moro?"` (passada sem alteração) |
| `pattern_queries` | Padrões SQL LIKE para agregação | `["person:%"]` (de "quem são meus amigos?") |
| `broad_query` | Se deve expandir o escopo do graph | `true` para "me conte tudo sobre..." |
| `reason` | Explicação da estratégia | `"deterministic"`, `"aggregation"`, `"broad"`, `"greeting"` |

### Entity Resolution

Quando você pergunta "Onde o Carlos mora?", o pipeline precisa descobrir que "Carlos" significa a entidade `person:carlos` no banco. Ele usa dois métodos determinísticos:

1. **Resolução determinística (primária)** — Faz match de palavras da query contra aliases de entidades conhecidas (`MemoryEntityAlias`), display names (`MemoryEntity.display_name`) e slugs de entity_keys. Rápido (< 10ms), confiável, custo zero de LLM. Por exemplo, "Onde o Carlos mora?" resolve deterministicamente para `person:carlos` via slug match.

2. **Query expansion (priming de aliases)** — `expand_query()` resolve aliases e busca vizinhos 1-hop no KG, adicionando entidades relacionadas.

Ambas as fontes são **unificadas** antes do graph gate. Se qualquer fonte identificar uma entidade, o graph traversal roda.

O trace step `"retrieval"` inclui um breakdown `entities_sources` mostrando quais entidades vieram de cada fonte (`deterministic`, `expansion`).

### Detecção de Agregação

Para queries como "quem são meus amigos?" ou "meus projetos", o planner faz match de keywords contra prefixos do schema e gera padrões SQL LIKE (ex: `person:%`). Isso só dispara quando o prefixo realmente existe no schema do usuário.

### Estratégia Skip

Para saudações e mensagens casuais ("oi", "bom dia", "e aí"), o planner retorna `strategy: "skip"` via regex matching, fazendo short-circuit no pipeline. Sem queries no banco, sem chamadas LLM, resposta instantânea.

### Resolução de Anáfora (Responsabilidade do Caller)

Se sua query contém pronomes ("Onde **ela** mora?"), o Arandu SDK **não** resolve. Resolução de pronomes depende do contexto de conversa (memória de curto prazo), que é domínio do caller. Resolva pronomes antes de chamar `retrieve()`:

```python
# O caller (seu agente) resolve "ela" → "Ana" usando contexto de conversa
resolved_query = "Onde a Ana mora?"  # não "Onde ela mora?"
result = await memory.retrieve(agent_id="user_123", query=resolved_query)
```

!!! info "Paralelo com neurociência"
    O planner 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 detecta padrões de query como pistas que guiam os sinais de retrieval.

??? example "Passo a passo: ciclo de vida completo de uma query"
    **Query:** "Onde o Marcos Tavares mora?"

    **Estágio 1 — Planejamento (determinístico):**
    ```
    Check saudação: sem match → prossegue
    Check agregação: sem match → sem pattern queries
    Check broad: sem match → broad_query = false
    Resolver determinístico: "marcos tavares" → person:marcos_tavares (slug match)
    similarity_query = "Onde o Marcos Tavares mora?" (original, sem alteração)
    ```

    **Estágio 2 — Retrieval multi-signal (paralelo):**
    ```
    Semântico: embedding("Onde o Marcos Tavares mora?") → top match: "Marcos Tavares lives in Porto Alegre" (0.91)
    Keyword: "marcos" + "tavares" → 4 fatos sobre Marcos
    Graph: BFS a partir de person:marcos_tavares → encontra fatos via entity links + relationships
    ```

    **Estágio 3 — Enhancement:**
    ```
    Spreading activation: a partir do seed "lives in Porto Alegre" → encontra fatos relacionados:
      "Marcos Tavares is married to Carolina" (via entity hop)
      "Carolina is an architect" (via 2-hop)
    ```

    **Estágio 4 — Reranking (blend multiplicativo):**
    ```
    "Marcos Tavares lives in Porto Alegre" → formula=0.91, reranker=1.0 → final=0.91
    "Marcos Tavares is a product manager at Vertix" → formula=0.65, reranker=0.2 → final=0.28
    "Carolina is an architect" → formula=0.30, reranker=0.0 → final=0.09 → filtrado (< 0.15)
    ```

    **Estágio 5 — Formatação:**
    ```
    Known facts:
    - Marcos Tavares lives in Porto Alegre
    ```

    **Resultado:** 1 fato altamente relevante, 210ms, 800 tokens.

---

## Estágio 2: Retrieval Multi-Signal

**Em português claro:** O pipeline busca fatos relevantes usando três métodos diferentes ao mesmo tempo - por significado, por palavras exatas e por conexões entre entidades. Isso captura fatos que qualquer método sozinho perderia.

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(dedup + 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 (passada sem alteração pelo planner)
- Busca na tabela `MemoryFact` com índice HNSW
- Retorna top-N candidatos acima do threshold `min_similarity`
- Filtros: `agent_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`
- **Hop decay**: Fatos no Hop 1 recebem score completo (1.0×). Fatos no Hop 2 recebem penalidade de 0.5×. Isso evita que fatos distantes dominem o pool de candidatos.
- Fatos são buscados via **entity links** (`MemoryFactEntityLink`), não só pelo `entity_key` primário. Isso significa que um fato "Clara saiu da Vertix" (subject primário: Clara) também é encontrado ao buscar sobre Vertix - porque o fato tem um entity link secundário pra Vertix.
- Fórmula de scoring: `edge_strength × recency_factor × edge_recency_factor × query_bonus × hop_decay`
- `query_bonus`: 1.5x quando o nome da entidade aparece no texto da query
- **Fallback**: se a tabela de entity links está vazia (pré-migration), retrieval faz fallback pra match direto por `entity_key`

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.

??? example "Passo a passo: retrieval cross-entity via entity links"
    **Query:** "O que aconteceu com a Vertix?"

    **Sem entity links (comportamento antigo):**
    ```
    Graph começa de organization:vertix
    Busca MemoryFact WHERE entity_key = 'organization:vertix'
    Encontra: apenas fatos onde Vertix é o subject PRIMÁRIO
    Perde: "Clara Rezende left Vertix" (entity_key = person:clara_rezende)
    ```

    **Com entity links (comportamento atual):**
    ```
    Graph começa de organization:vertix
    Busca MemoryFactEntityLink WHERE entity_key = 'organization:vertix'
    Encontra fact_ids linkados à Vertix, independente do subject primário:
      → "Clara Rezende left Vertix" (primário: Clara, link: Vertix) ✅
      → "Vertix received Series A of R$ 20M" (primário: Vertix) ✅
      → "Ricardo Gomes is co-founder of Vertix" (primário: Ricardo, link: Vertix) ✅
      → "Vertix signed contract with Ambev" (primário: Vertix) ✅
    ```

    **Resultado:** 4 fatos encontrados vs 2 sem links. A query sobre Vertix traz fatos de Clara, Ricardo e o deal da Ambev - todos linkados à Vertix mas não primariamente sobre a Vertix.

### 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:

!!! warning "O reranker faz blend com estes pesos"
    Por padrão, `enable_reranker=True` - o reranker LLM usa um blend multiplicativo com o score da fórmula computado a partir destes pesos. O score da fórmula continua sendo importante porque o reranker só pode atenuar ou amplificar, nunca zerar. Configure `enable_reranker=False` para depender apenas destes pesos no ranking final.

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

### Breakdown Completo do Score

Cada fato é pontuado em múltiplas dimensões. Você pode inspecionar em `fact.scores` pra entender **por que** um fato ficou onde ficou no ranking:

Cada dict `ScoredFact.scores` contém **todos os 6 sinais** computados ao longo do pipeline. A fórmula ponderada acima produz o score combinado base; estágios posteriores adicionam sinais que podem modificar o ranking final:

| Chave | Origem | Range | Descrição |
|-------|--------|-------|-----------|
| `semantic` | Busca semântica | 0.0 - 1.0 | Similaridade de cosseno entre embeddings da query e do fato. Sinal primário de retrieval. |
| `keyword` | Busca por keyword | 0.0 - 1.0 | Fração de palavras da query encontradas no texto do fato. Complementa semântico para matches exatos. |
| `recency` | Merge & Rank | 0.0 - 1.0 | Decay exponencial a partir do `created_at`, half-life = `recency_half_life_days` (padrão 14). |
| `importance` | Importância dinâmica | 0.0 - 1.0 | Valor raw de importância do banco de dados. Computado pelo job de importância em background a partir de frequência de retrieval, recência de uso, correções do usuário e participação em padrões. Começa em 0.5 para fatos novos e evolui ao longo do tempo. |
| `confidence` | Merge & Rank | 0.0 - 1.0 | Confiança efetiva após decay temporal. Presente no dict `scores` para debugging, mas NÃO faz parte da fórmula ponderada (`score_weights` usa apenas `semantic`, `recency`, `importance`). A confiança base é atribuída pelo LLM durante extração (tipicamente 0.95 para afirmações assertivas). Decai ao longo do tempo e é usada como filtro (`min_confidence`). |
| `reranker` | Reranking | 0.0 - 1.0 | Score de relevância baseado em LLM. Presente apenas quando `enable_reranker=True`. Float contínuo retornado pelo reranker LLM. |

Sinais adicionais computados durante enhancement (não estão em `score_weights` mas afetam o score final):

| Chave | Origem | Descrição |
|-------|--------|-----------|
| `pattern` | Enhancement | Boost aditivo para fatos com alto `reinforcement_count` (até +0.10). |
| `graph` | Graph traversal | Score do traversal BFS de 2 saltos em relacionamentos de entidades. |

### 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 |
| `min_score` | `0.15` | Score final mínimo para fatos retornados |
| `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

**Em português claro:** Depois de encontrar os resultados iniciais, o pipeline segue conexões pra descobrir fatos relacionados. Se você pergunta sobre uma pessoa, ele pode puxar fatos sobre o trabalho dela, projetos e equipe - coisas que você não perguntou diretamente mas que adicionam contexto útil.

### 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). Defina como `0` para desabilitar spreading completamente.
- Aplica fator de decay por hop (`spreading_decay_factor`, padrão 0.50). Hop 1 usa o fator diretamente; Hop 2 usa o fator ao quadrado (decay composto).
- Retorna até `spreading_facts_per_entity` fatos adicionais por entidade (padrão 3), aplicado tanto no Hop 1 quanto no Hop 2.

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.

!!! tip "Quando o spreading activation faz diferença?"
    O spreading tem mais impacto com **20+ entidades** e relações cruzadas entre domínios (ex: pessoas → projetos → clientes → tecnologias). Com datasets pequenos (< 15 entidades), os sinais semântico, keyword e graph já cobrem todo o espaço de fatos - o spreading pode retornar candidatos mas eles serão dedupados contra os resultados existentes. Os campos do trace `spreading_candidates_returned` e `spreading_candidates_unique` permitem confirmar se o spreading está contribuindo fatos novos pro seu dataset.

### Sinal de Padrão

Fatos com alto `reinforcement_count` (incrementado por decisões NOOP no write) recebem um boost aditivo no score:

- Alto reinforcement count → 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. Defina como `0` para desabilitar. |
| `spreading_decay_factor` | `0.50` | Decay de score por hop. Hop 1 = fator, Hop 2 = fator² |
| `spreading_facts_per_entity` | `3` | Máximo de fatos buscados por entidade no Hop 1 e Hop 2 |
| `spreading_max_related_entities` | `5` | Máximo de entidades KG-relacionadas exploradas no Hop 1 |

---

## Estágio 4: Reranking (Opcional)

**Em português claro:** Os estágios anteriores encontram fatos relevantes, mas o ranking é baseado em matemática (scores de similaridade, keyword overlap). O reranker pergunta a um LLM: "Dado o que essa pessoa tá perguntando, quais desses fatos são realmente mais úteis?" Isso produz um ranking final mais inteligente.

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

- O reranker avalia **2× `topk_facts`** candidatos (padrão: 40). Esse pool expandido garante que fatos semanticamente relevantes além do top-20 inicial cheguem no reranker. Aumente `topk_facts` pra expandir a cobertura do reranker.
- 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 ou exceder `reranker_timeout_sec` (padrão 5.0s), o ranking original é preservado
- O timeout é enforced via `asyncio.wait_for` - a chamada LLM é cancelada se exceder o timeout configurado
- Usa o mesmo provider LLM configurado no client (não precisa de provider separado)

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

### Configuração

| Parâmetro | Tipo | Default | Descrição |
|-----------|------|---------|-----------|
| `reranker_weight` | float | `0.70` | Peso do score do reranker no blend multiplicativo |
| `min_reranker_score` | float | `0.10` | Score mínimo do reranker; fatos abaixo são eliminados |
| `reranker_timeout_sec` | float | `5.0` | Timeout para a chamada LLM do reranker (segundos) |

!!! warning "Veto do reranker: `min_reranker_score`"
    Quando `enable_reranker=True`, qualquer fato que recebe um score do reranker **abaixo** de `min_reranker_score` (padrão `0.10`) é eliminado dos resultados (final_score definido como 0.0). Isso dá ao reranker poder de veto sobre fatos completamente irrelevantes - mesmo que o score da fórmula seja alto (ex: graph BFS dá 0.80 pra um fato distante e não relacionado). Quando `enable_reranker=False`, esta configuração não tem efeito. Ajuste: `config_overrides={"min_reranker_score": 0.05}` para resultados mais permissivos, `0.20` para filtragem mais rigorosa.

!!! info "Scoring por multiplicative blend"
    O reranker NÃO substitui o score da fórmula. Ele usa um **blend multiplicativo**:

    `final_score = formula_score × (floor + reranker_weight × reranker_score)`

    onde `floor = 1 - reranker_weight`. Com o default `reranker_weight=0.70`, um fato com formula=0.9 e reranker=0.0 fica com final = 0.9 × (0.30 + 0) = 0.27 (não 0.0). Um fato com formula=0.9 e reranker=1.0 fica com final = 0.9 × (0.30 + 0.70) = 0.90. O reranker pode amplificar ou atenuar fatos mas **não consegue zerar** um fato com bons sinais de retrieval apenas pelo blend. Porém, `min_reranker_score` É uma exceção - fatos com score abaixo dele são zerados independentemente do score da fórmula. O dict `scores` preserva tanto `formula` (pré-reranker) quanto `reranker` (score do LLM) pra debugging.

??? example "Passo a passo: como o blend do reranker funciona"
    **Query:** "Qual o time de futebol do Bruno Almeida?"

    **Candidatos pré-reranker (scores da fórmula):**
    ```
    1. "Bruno Almeida runs marathons" → formula = 0.45 (semântico: similaridade com "sports")
    2. "Bruno Almeida developed an ML model" → formula = 0.38
    3. "Bruno Almeida works at Orion Tech" → formula = 0.35
    ```

    **Scores do reranker (avaliação do LLM):**
    ```
    1. "Bruno Almeida runs marathons" → reranker = 0.0 (maratona ≠ futebol)
    2. "Bruno Almeida developed an ML model" → reranker = 0.0 (irrelevante)
    3. "Bruno Almeida works at Orion Tech" → reranker = 0.0 (irrelevante)
    ```

    **Blend multiplicativo** (weight=0.70, floor=0.30):
    ```
    1. final = 0.45 × (0.30 + 0.70 × 0.0) = 0.45 × 0.30 = 0.135 → filtrado (< 0.15)
    2. final = 0.38 × 0.30 = 0.114 → filtrado
    3. final = 0.35 × 0.30 = 0.105 → filtrado
    ```

    **Resultado:** 0 fatos retornados. Correto - não existe informação sobre o time de futebol do Bruno na memória.

    **Compare com uma query relevante** - "O que o Bruno Almeida desenvolveu?":
    ```
    "Bruno developed an ML model for fraud detection" → formula=0.92, reranker=1.0
    final = 0.92 × (0.30 + 0.70 × 1.0) = 0.92 × 1.0 = 0.92 ✅
    ```

---

## Estágio 5: Formatação

**Em portugues claro:** O pipeline pega os fatos ranqueados e organiza numa string limpa, pronta pra consumo por LLM. O formato foi projetado para ser direto e sem ruido -- fatos aparecem como uma lista simples em "Known facts:", conversas relevantes em "Relevant conversations:", e padroes observados em "Observed patterns:". Tudo dentro de um orcamento de tokens pra nao estourar seu prompt.

### Formato de Contexto

O contexto e organizado em tres secoes dentro de um orcamento de tokens (`context_max_tokens`):

!!! note "`context_max_tokens` e um orcamento proporcional, nao um limite rigido"
    O parametro `context_max_tokens` controla o tamanho **relativo** do contexto de saida, mas a contagem real de tokens pode exceder o valor configurado. O pipeline garante um contexto minimo para fatos e usa o parametro como orcamento proporcional entre secoes. Trate como um target, nao um limite estrito.

| Secao | Conteudo |
|-------|----------|
| **Known facts** | Lista limpa de fatos, ordenados por score. Sem timestamps, sem prefixo de entidade, sem qualificadores como "assert confidently" |
| **Observed patterns** | Meta-observations (padroes, tendencias, insights detectados pelos background jobs) |
| **Relevant conversations** | Texto original de eventos recentes (ate 300 caracteres cada), com data |

O formato foi projetado para **consumo direto por LLMs** -- limpo, sem ruido, sem metadados desnecessarios.

!!! note "Perfis de entidade nao sao injetados no contexto"
    Perfis de entidade (`profile_text`) sao usados internamente pelo write pipeline como contexto para a extracao informada. Eles **nao** sao incluidos no contexto de retrieval retornado por `retrieve()`. O retrieval retorna apenas fatos individuais, padroes e eventos.

### Configuracao

| Parametro | Default | Descricao |
|-----------|---------|-----------|
| `context_max_tokens` | `2000` | Maximo de tokens no contexto formatado |
| `hot_tier_ratio` | `0.50` | Parcela do orcamento para fatos com scores mais altos |
| `warm_tier_ratio` | `0.30` | Parcela do orcamento para fatos de suporte |

### Formato de Output

A string `context` e formatada para injecao direta em prompts LLM:

```
Known facts:
- Lives in Sao Paulo
- Works at Acme Corp as a backend engineer
- Wife's name is Ana

Observed patterns:
- Prefers working late at night

Relevant conversations:
- (2026-03-28) Had a great meeting with the product team about the roadmap...
```

---

## RetrieveResult

```python
result = await memory.retrieve(agent_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.fact_text}")
    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["Planner Determinístico\n(regex + schema)"]
    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 neurociencia"
    O orcamento de tokens age como o limite de capacidade da working memory. Fatos com scores mais altos recebem prioridade (foco de atencao), enquanto padroes e eventos fornecem contexto de suporte. Isso espelha o modelo de processos embutidos de Cowan, onde um numero pequeno de itens esta no foco de atencao, cercado por memoria de longo prazo ativada.
