# PRD: Write Pipeline — Observability + Empty Message Guard

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

## Problem

O time de validação (Round 2) identificou 3 gaps de baixa severidade no write pipeline:

1. **Mensagens vazias gastam LLM call** (Gap #19) — Uma mensagem com só espaços/whitespace passa pelo pipeline inteiro, faz chamada de embedding + LLM, e retorna vazio após ~2s. Deveria retornar imediatamente sem consumir recursos.

2. **PipelineTrace não expõe detalhes internos** (Gap #18) — O trace do extraction step não inclui `effective_mode` (se caiu de multi → single por threshold), `batch_count`, `input_classification` (SHORT/MEDIUM/LONG), nem se houve retry de relation. Dificulta debug de config avançada.

3. **Relation retry não observável** (Gap #20) — O retry automático de relations (implementado na v0.5.5) não aparece no trace. Dev não sabe se o retry disparou nem se o segundo call resolveu.

Nenhum é bug funcional, mas o #19 desperdiça dinheiro do cliente e os #18/#20 impedem observabilidade adequada.

## Target Audience

Desenvolvedores integrando o Arandu SDK que usam `verbose=True` para debugar o pipeline.

## Proposed Solution

1. **Early return** em `run_write_pipeline()` se `message.strip()` é vazio — retorna `WritePipelineResult` com success=True, zero facts, sem criar evento nem chamar LLM.

2. **Enriquecer trace do extraction step** com campos adicionais: `effective_mode`, `batch_count`, `input_class`, `relation_retry_triggered`.

3. **Retornar info de retry** do `_extract_relations()` para que o pipeline consiga incluir no trace.

## Success Criteria

- Mensagem vazia/whitespace retorna em <5ms sem criar evento no banco.
- Trace do extraction step contém `effective_mode`, `batch_count`, `relation_retry_triggered`.
- Teste automatizado valida cada comportamento.

## Scope v0

1. Guard clause para mensagem vazia no topo de `run_write_pipeline()`
2. Campos extras no trace data do step "extraction"
3. `_extract_relations()` retorna tupla ou flag indicando se retry ocorreu
4. `_multi_pass_extract()` propaga info de retry e batch count no output
5. Testes unitários para cada item

## Anti-scope

- Não criar novo step de trace (usar o step "extraction" existente)
- Não mudar interface pública (`WriteResult`, `PipelineTrace`, `PipelineStep`)
- Não adicionar config nova (tipo `skip_empty_messages`)
- Não mexer no read pipeline
- Não mexer no single-pass extraction
- Não adicionar campo `input_class` — é derivável do tamanho da mensagem no client, não precisa estar no SDK

## Technical Context

- `run_write_pipeline()` em `src/arandu/write/pipeline.py` é o orquestrador
- Trace já existe via `PipelineTrace.add_step()` — basta adicionar campos no dict `data`
- `_extract_relations()` em `src/arandu/write/extract.py` já faz retry mas não sinaliza
- `_multi_pass_extract()` sabe o batch count e se fez fallback pra single-pass
- Padrão fail-safe: guard clause retorna resultado vazio, não levanta exceção

## Open Questions

None.
