# PRD: Verbose Mode — Dados intermediários dos pipelines

> Generated via /vibeflow:discover on 2026-03-18

## Problem

O arandu tem um write pipeline de 4 estágios (Extract → Entity Resolution → Reconcile → Upsert) e um read pipeline de 5+ estágios (Plan → Multi-Signal Retrieval → Rerank → Top-K → Context Compression), mas `write()` retorna só `WriteResult` e `retrieve()` retorna só `RetrieveResult`. Não existe forma de ver os dados intermediários de cada estágio — o que foi extraído, como entidades foram resolvidas, quais scores cada fato recebeu, se o reranker mudou a ordem, etc.

Isso impede debugging efetivo e impossibilita ferramentas de inspeção (como o futuro Arandu Inspector) de mostrar o pipeline passo a passo. Hoje, pra entender o que aconteceu internamente, o dev precisa colocar prints no código-fonte.

## Target Audience

1. O desenvolvedor do arandu (debug durante desenvolvimento)
2. Devs que integram o arandu e querem entender o comportamento (por que um fato foi ADD vs UPDATE? por que um retrieve rankeou X acima de Y?)
3. O futuro Arandu Inspector (ferramenta visual que consome esses dados)

## Proposed Solution

Adicionar um parâmetro `verbose=True` (opt-in) a `write()` e `retrieve()`. Quando ativo, o resultado inclui um campo `pipeline` com dados intermediários de cada estágio. A API existente não muda — quem não passa `verbose` continua recebendo exatamente o que recebe hoje.

Internamente, um objeto `PipelineTrace` é passado pelos estágios do pipeline. Cada estágio registra seus dados no trace. No final, o trace é incluído no resultado se `verbose=True`.

## Success Criteria

1. `result = await memory.write(..., verbose=True)` retorna `result.pipeline` com dados de cada estágio (extraction, entity_resolution, reconciliation, upsert)
2. `result = await memory.retrieve(..., verbose=True)` retorna `result.pipeline` com dados de cada estágio (planning, retrieval, reranking, scoring, compression)
3. `result = await memory.write(...)` (sem verbose) continua retornando exatamente o mesmo que antes — zero breaking changes
4. Cada estágio do trace inclui `duration_ms` pra saber quanto tempo levou
5. Os dados do trace são serializáveis (dict/JSON) pra facilitar consumo por APIs

## Scope v0

- [ ] Criar dataclass `PipelineTrace` com lista de `PipelineStep` (name, duration_ms, data)
- [ ] Adicionar parâmetro `verbose: bool = False` a `MemoryClient.write()` e `MemoryClient.retrieve()`
- [ ] Adicionar campo opcional `pipeline: PipelineTrace | None` a `WriteResult` e `RetrieveResult`
- [ ] Instrumentar `run_write_pipeline()` pra coletar dados de cada estágio:
  - **extraction:** mode, entities extraídas, facts extraídos, relations, dedup stats
  - **entity_resolution:** por entidade: método usado, fuzzy_score, is_new, entity_key
  - **reconciliation:** por fato: action, matched_fact_id, matched_fact_text, similarity_score
  - **upsert:** added/updated/confirmed/deleted counts, fact IDs criados
- [ ] Instrumentar `run_read_pipeline()` pra coletar dados de cada estágio:
  - **planning:** strategy, entities, similarity_query, broad_query
  - **retrieval:** candidates_count, semantic_count, keyword_count, graph_count
  - **reranking:** applied (bool), score_changes (antes/depois por fato)
  - **scoring:** por fato no top-K: breakdown completo (semantic, keyword, recency, graph, confidence)
  - **compression:** token_budget, tokens_used, facts_included count
- [ ] Adicionar método `to_dict()` ao PipelineTrace pra serialização JSON
- [ ] Testes unitários pro trace (write verbose e retrieve verbose)

## Anti-scope

- **Streaming/SSE dos estágios em tempo real** — v0 espera o pipeline completar e retorna tudo junto
- **Persist do trace no banco** — é ephemeral, retornado no resultado e descartado
- **Trace dos background jobs** (clustering, consolidation, etc.) — só write e retrieve
- **Log automático quando verbose=True** — o trace é retornado, não logado. Quem consome decide o que fazer
- **Verbose mode no FastAPI server** — a server extension do arandu não é escopo; só o client
- **Mudança nas assinaturas internas dos estágios** — o trace é passado como parâmetro opcional; estágios que não recebem o trace continuam funcionando igual
- **Alteração nos prompts LLM** — o verbose mode captura dados, não muda comportamento

## Technical Context

### Pipeline de Write (src/arandu/write/pipeline.py)
- `run_write_pipeline()` chama em sequência: `extract_from_message()` → `resolve_entities()` → `reconcile_facts()` → `execute_upsert()` + `upsert_relations()`
- Cada função retorna dataclasses/dicts que já contêm os dados intermediários — o trace captura esses retornos antes de descartá-los
- O `MemoryClient.write()` chama `run_write_pipeline()` e mapeia o dict para `WriteResult`

### Pipeline de Read (src/arandu/read/pipeline.py)
- `run_read_pipeline()` chama: `plan_retrieval()` → `expand_query()` → `retrieve_candidates()` (semantic+keyword+graph em paralelo) → `rerank_with_llm()` → top-K selection → `compress_context()`
- `ScoredFact.scores` já contém o breakdown (semantic, keyword, recency, etc.) — o trace captura isso
- O `MemoryClient.retrieve()` chama `run_read_pipeline()` e mapeia para `RetrieveResult`

### Design Pattern: PipelineTrace
- O trace é um objeto mutável passado como parâmetro opcional `trace: PipelineTrace | None = None`
- Cada estágio faz `if trace: trace.add_step(name, data, duration_ms)` — zero overhead quando trace é None
- No final, `MemoryClient` inclui o trace no resultado se `verbose=True`
- `PipelineTrace` e `PipelineStep` ficam em `src/arandu/tracing.py` (novo arquivo)

### Arquivos que precisam ser modificados
- `src/arandu/tracing.py` (NOVO) — PipelineTrace, PipelineStep
- `src/arandu/client.py` — adicionar verbose param, passar trace, incluir no resultado
- `src/arandu/constants.py` — adicionar campo pipeline a WriteResult e RetrieveResult
- `src/arandu/write/pipeline.py` — aceitar trace, registrar dados de cada estágio
- `src/arandu/read/pipeline.py` — aceitar trace, registrar dados de cada estágio
- Testes: `tests/test_verbose.py` (NOVO)

## Open Questions

Nenhuma. Todas as decisões foram tomadas.
