# Spec: Token Accumulation + config_overrides write + dry_run (Benchmark Infra Part 2)

> Generated via /vibeflow:gen-spec on 2026-03-22
> PRD: `.vibeflow/prds/benchmark-infrastructure.md`
> Budget: ≤ 6 files
> Dependencies: `.vibeflow/specs/benchmark-infra-part-1.md`

## Objective

`WriteResult` e `RetrieveResult` expõem `tokens_used` com contagem real de tokens, `write()` aceita `config_overrides` e `dry_run`.

## Context

Após Part 1, `LLMProvider.complete()` retorna `LLMResult` com `usage`. Mas nenhum pipeline acumula esses values — eles são descartados. Esta spec conecta a usage ao resultado público e adiciona as duas features de benchmark: config override e dry run.

## Definition of Done

1. **[ ] `tokens_used` no `WriteResult`** — `WriteResult.tokens_used: TokenUsage` com contagem total de tokens de todas as chamadas LLM no write pipeline. Valor > 0 para mensagens não-vazias.
2. **[ ] `tokens_used` no `RetrieveResult`** — Mesmo campo no `RetrieveResult`. Valor > 0 quando reranker ou planner usam LLM.
3. **[ ] `config_overrides` no `write()`** — `memory.write(user_id, msg, config_overrides={...})` faz merge com config do client pra aquela request. Mesma mecânica do `retrieve()`.
4. **[ ] `dry_run` no `write()`** — `memory.write(user_id, msg, dry_run=True)` retorna `WriteResult` com facts extraídos mas nada persiste no banco.
5. **[ ] Testes passam** — Todos os testes existentes + novos testes pra tokens_used, config_overrides write, e dry_run.
6. **[ ] Docs EN + PT-BR** — Seções de WriteResult, RetrieveResult, write() API atualizadas.

## Scope

### 1. `src/arandu/client.py` — Acumulação + novas features

**tokens_used no write:**
- Criar `TokenUsage()` acumulador antes de chamar o pipeline
- Passar acumulador pro pipeline (ou extrair dos `LLMResult`s)
- Popular `WriteResult.tokens_used` no final

**tokens_used no retrieve:**
- Mesma mecânica. Planner e reranker usam LLM.
- Popular `RetrieveResult.tokens_used`

**config_overrides no write():**
- Reusar `_apply_config_overrides()` existente
- `async def write(self, user_id, message, *, config_overrides=None, dry_run=False, ...)`

**dry_run:**
- Quando `dry_run=True`, passar flag pro write pipeline pra pular upsert
- Não criar evento, não persistir nada
- Retornar `WriteResult` com extraction results + tokens_used

### 2. `src/arandu/write/pipeline.py` — dry_run support

- Aceitar parâmetro `dry_run: bool = False`
- Quando True: rodar extraction + entity resolution + reconciliation, pular upsert
- Retornar `WritePipelineResult` normalmente (com facts que seriam criados)

### 3. `tests/test_client.py` ou `tests/test_write_pipeline.py` — Novos testes

- `test_write_tokens_used_populated`
- `test_retrieve_tokens_used_populated`
- `test_write_config_overrides_extraction_mode`
- `test_write_dry_run_no_persist`

### 4-5. `docs/en/` + `docs/pt-BR/` — Docs

- WriteResult: documentar `tokens_used`
- RetrieveResult: documentar `tokens_used`
- write() API: documentar `config_overrides` e `dry_run`
- Getting Started: mencionar `tokens_used` como debug/benchmark tool

## Anti-scope

- **Per-step token breakdown** — Total é suficiente.
- **Embedding token tracking** — Barato e previsível.
- **Métricas de custo em dólar** — Provider-specific. SDK expõe tokens.
- **Benchmark runner/CLI** — Inspector cria o endpoint deles.
- **config_effective no WriteResult** — Pode vir depois se inspector pedir.

## Technical Decisions

### TD-1: Acumulação via `_TokenTrackingLLM` wrapper vs passagem explícita
**Trade-off:** Wrapper é transparente mas adiciona indireção. Passagem explícita é mais trabalho mas mais claro.
**Decisão:** Wrapper interno `_TokenTrackingLLM` no `client.py`. Wrapa o `LLMProvider` do user, delega `complete()`, e acumula `result.usage` num counter. Após a pipeline call, lê o counter. Zero mudança nos pipelines.

### TD-2: dry_run pula upsert vs rollback de transaction
**Trade-off:** Pular upsert é mais limpo — menos DB work. Rollback é mais fiel ao comportamento real.
**Decisão:** Pular upsert. O objetivo é benchmark de extraction, não de DB performance. Menos risco de side effects.

### TD-3: dry_run não cria MemoryEvent
**Trade-off:** Poderia criar e depois deletar. Ou não criar.
**Decisão:** Não criar evento. dry_run = zero side effects. O caller quer comparar modos de extraction sem poluir o banco.

## Applicable Patterns

- **Pipeline Orchestration** — dry_run é um flag no pipeline, não um pipeline separado.
- **Dependency Injection** — `_TokenTrackingLLM` implementa `LLMProvider`, wrapa o provider real.
- **Error Handling** — Se usage for `None` (provider não reporta), `tokens_used` fica zerado. Não falha.
- **Testing** — FakeLLMProvider já retorna `LLMResult` (Part 1). Testes de acumulação mockam usage values.

## Risks

1. **dry_run + entity resolution pode ler DB** — Mitigação: entity resolution é read-only (busca existing entities). Pode rodar em dry_run sem side effects. Só o upsert persiste.
2. **`_TokenTrackingLLM` wrapper overhead** — Mitigação: wrapper é thin (3 linhas no `complete()`). Overhead negligível.
