> ## Documentation Index
> Fetch the complete documentation index at: https://chatcli.edilsonfreitas.com/llms.txt
> Use this file to discover all available pages before exploring further.

# #3 Reflexion — Aprender com Falhas

> Após erro, alucinação ou qualidade baixa, uma chamada LLM destila uma lição estruturada (situation / mistake / correction) e persiste em memory.Fact. Próximas tarefas similares a recuperam via RAG — fechando o loop sem retreinamento.

**Reflexion** fecha o loop de aprendizado: quando um agente falha ou produz saída de baixa qualidade, em vez de perder a experiência, o pipeline gera uma **Lesson** estruturada e persiste na memória de longo prazo. Na próxima tarefa similar, essa lição emerge naturalmente via RAG+HyDE.

<Info>Reflexion é **o único post-hook ligado por default** — porque só dispara em condições excepcionais (erro, discrepância) e o trabalho de geração de lição nunca bloqueia o turn do usuário.</Info>

<Warning>**Modo durável (default desde Abr/2026):** triggers passam por uma fila WAL-backed com worker pool e dead-letter queue. Lições sobrevivem a crash do processo via replay no próximo boot. Ver [Fila Durável](#fila-dur-vel-wal-worker-pool-dlq).</Warning>

***

## O que é uma Lesson

Uma `Lesson` é um registro de quatro linhas:

```go theme={"system"}
type Lesson struct {
    Situation  string   // "Quando preciso editar arquivo Go grande..."
    Mistake    string   // "Tentei reescrever o arquivo todo de uma vez"
    Correction string   // "Use Edit tool com old_string/new_string específicos"
    Tags       []string // ["go", "edit-file", "large-file", "reflexion"]
    Trigger    string   // "error" | "hallucination" | "low_quality" | "manual"
    CreatedAt  time.Time
}
```

Ao persistir em `memory.Fact`, o `Content` fica:

```text theme={"system"}
LESSON: Quando preciso editar arquivo Go grande
MISTAKE: Tentei reescrever o arquivo todo de uma vez
CORRECTION: Use Edit tool com old_string/new_string específicos
TRIGGER: error
```

A categoria do Fact é `lesson` e as tags incluem `reflexion` + `trigger:<x>` + os tags específicos do domínio. Isso permite queries precisas: "me mostre todas as lições sobre edit-file" se torna uma pesquisa normal da memória.

***

## Quatro gatilhos

<Tabs>
  <Tab title="OnError">
    ```go theme={"system"}
    if cfg.OnError && result.Error != nil {
        return "error"
    }
    ```

    O worker retornou `Error != nil`. Exemplos: timeout, tool call inválido, crash do provedor.
    **Default: ON.**
  </Tab>

  <Tab title="OnHallucination">
    ```go theme={"system"}
    if cfg.OnHallucination && result.MetadataFlag("verified_with_discrepancy") {
        return "hallucination"
    }
    ```

    O `VerifyHook` (#6 CoVe) flagou discrepância entre o rascunho e as respostas às perguntas de verificação.
    **Default: ON.**
  </Tab>

  <Tab title="OnLowQuality">
    ```go theme={"system"}
    if cfg.OnLowQuality && result.MetadataFlag("refine_low_quality") {
        return "low_quality"
    }
    ```

    O `RefineHook` (#5) deu nota baixa no rascunho original.
    **Default: OFF** (barulhento demais por default).
  </Tab>

  <Tab title="Manual via /reflect">
    ```go theme={"system"}
    if result.MetadataFlag(MetaForceReflexion) {
        return "manual"
    }
    ```

    Slash `/reflect <free-text>` persiste direto, sem chamada LLM.
    **Sempre disponível.**
  </Tab>
</Tabs>

***

## Fluxo — modo durável (default)

Com `CHATCLI_QUALITY_REFLEXION_QUEUE_ENABLED=true` (default), o trigger vai pra uma fila persistente. O hook **não bloqueia o turn** e o processo pode crashar sem perder a lição:

<Steps>
  <Step title="PostRun inspeciona o trigger">
    `ReflexionHook.PostRun(ctx, hc, result)` olha `result.Metadata` + `result.Error` — se nenhum gate bate, retorna em μs.
  </Step>

  <Step title="WAL Append (síncrono, sub-ms)">
    O hook chama `enqueuer.Enqueue(req)`. O Runner calcula um `JobID = sha256(task|trigger|attempt)[:16]`, escreve um record no WAL (`~/.chatcli/reflexion/wal/<id>.wal`) via **tmp → fsync → atomic rename → dir fsync**, então empilha em memory.
  </Step>

  <Step title="Retorno imediato ao pipeline">
    PostRun retorna nil; o turn do usuário continua sem espera. A latência adicional é o fsync (tipicamente \< 1 ms).
  </Step>

  <Step title="Worker pool processa async">
    Um dos N workers (default 2) deenfileira, chama `GenerateLesson` com timeout per-job (default 2 min) e persiste em `memory.Fact` se o LLM não emitir `<skip>`.
  </Step>

  <Step title="Classificação do outcome">
    Sucesso ou Skipped → ACK (delete WAL record). Transient error (timeout, 429/503) → reschedule com backoff exponencial + jitter. Permanent (parser error) → move pra DLQ imediatamente.
  </Step>

  <Step title="Replay on boot">
    Na próxima sessão, `Runner.Replay()` roda async e reenfileira todo record pendente do WAL (descartando os mais velhos que `StaleAfter`, default 7 dias).
  </Step>
</Steps>

<Tip>**Latência observável no turn:** um fsync local típico é \< 1 ms em SSD. A geração de lição em si (chamada LLM) acontece **depois** do turn responder — o usuário nunca espera.</Tip>

### Fallback: modo legado (goroutine detached)

Se `CHATCLI_QUALITY_REFLEXION_QUEUE_ENABLED=false`, o hook volta ao comportamento original:

```go theme={"system"}
go h.runReflexion(context.Background(), req)  // fire-and-forget
```

Zero dependência de filesystem, mas lições em vôo somem se o processo for morto. Mantido para compatibilidade e para usuários que preferem simplicidade sobre durabilidade.

***

## Fila Durável — WAL + Worker Pool + DLQ

A fila é implementada em `cli/agent/quality/lessonq/` com garantias enterprise:

### WAL (Write-Ahead Log)

Cada lição pendente é um arquivo `.wal` em `~/.chatcli/reflexion/wal/` — um por Job ID. Layout binário:

```text theme={"system"}
[4B magic 'LSN1'][4B length BE][4B CRC32 payload][N bytes JSON payload][4B CRC32 trailer]
```

* **CRC duplo** detecta torn writes (crash no meio do fsync). Records corruptos são descartados no replay + `chatcli_lessonq_wal_corruption_total` incrementa.
* **Atomic rename**: escrita em `<id>.tmp.<pid>.<seq>` → fsync → rename → dir fsync. Nunca um leitor vê record parcial.
* **O(1) ACK**: um único `unlink` remove o record. Sem compactação em background.

### Worker Pool

```text theme={"system"}
Queue (min-heap by NextAttemptAt)
      │
      ├─► Worker 1 ─► GenerateLesson ─► persist ─► ACK
      ├─► Worker 2 ─► GenerateLesson ─► persist ─► ACK
      └─► Worker N ─► ...
```

Cada worker:

* Dequeue bloqueante (espera por NextAttemptAt ≤ now).
* Per-job timeout bounded (não herda ctx do turn — reflexion outlive o turn por design).
* Panic recovery: se o processor panica, vai direto pra DLQ (retry não ajuda bug).
* Métrica `chatcli_lessonq_processing_duration_seconds{outcome}` emitida.

### Dead Letter Queue

Failures permanentes ou exaustão de retries vão pra `~/.chatcli/reflexion/dlq/` (mesmo formato WAL, read-only pro processo). Operador inspeciona e decide:

```bash theme={"system"}
/reflect failed              # lista com último erro
/reflect retry <job-id>      # reenfileira (reseta Attempts=0)
/reflect purge <job-id>      # remove definitivo
```

### Retry com Jitter

Transient errors (ctx timeout, provider 429/503, temp fs error) viram reschedule:

```text theme={"system"}
delay = InitialDelay × Multiplier^(attempt-1)
delay = min(delay, MaxDelay)
delay = delay × uniform(1-JitterFraction, 1+JitterFraction)
```

Defaults: 1s inicial, 5min cap, 2.0 multiplier, ±20% jitter, 5 tentativas. **Full jitter** previne thundering herd quando provider volta do fora-do-ar.

### Idempotência

`JobID` é conteúdo-endereçado: `sha256(normalized(task) | trigger | attempt | outcome)[:16]`. Re-trigger da mesma situação enquanto o job está in-flight é no-op (WAL existe → Runner pula queue insert). Whitespace é normalizado pra evitar inflação por churn trivial.

### Drain + Graceful Shutdown

Na saída (`cli.cleanup()`), o Runner fica em `DrainAndShutdown(30s)`:

1. Queue fecha — sem novos dequeues.
2. Workers terminam in-flight (ou são cancelados no timeout).
3. WAL/DLQ fecham.

Jobs ainda enfileirados sobrevivem no WAL e reprocessam no próximo boot. **Zero perda de dado em SIGTERM ou kill -9**.

***

## `/reflect` — Comandos

<CodeGroup>
  ```bash Status theme={"system"}
  /reflect                     # queue depth + DLQ size + subcommands hint
  ```

  ```bash Persistir lição manual (sem LLM) theme={"system"}
  /reflect quando editar arquivos Go grandes use Edit, não rewrite total
  # → grava direto em memory.Fact (trigger=manual)
  ```

  ```bash Listar pendentes + DLQ theme={"system"}
  /reflect list
  # pending (N):
  #   <id>  [trigger=...  attempts=N  age=Ns]
  #     task: <truncated>
  # dead letter queue (M):
  #   <id>  [trigger=...  attempts=N]
  #     last error: <truncated>
  ```

  ```bash Só DLQ (foco em investigação) theme={"system"}
  /reflect failed
  ```

  ```bash Reenfileirar job da DLQ (reseta attempts) theme={"system"}
  /reflect retry <job-id>
  # Tab complete puxa IDs reais da DLQ com preview da task
  ```

  ```bash Remover permanente da DLQ theme={"system"}
  /reflect purge <job-id>
  # Tab complete puxa IDs reais da DLQ
  ```

  ```bash Forçar replay do WAL theme={"system"}
  /reflect drain
  # Útil depois de importar WAL de outra máquina, ou pra reprocessar manualmente
  ```
</CodeGroup>

<Info>Todos os subcomandos têm autocomplete via Tab. `/reflect retry ` e `/reflect purge ` listam IDs reais vivos da DLQ com preview da task + último erro.</Info>

***

## Arquivos e layout

```text theme={"system"}
~/.chatcli/reflexion/
├── wal/                          # fila ativa (pendentes + in-flight)
│   ├── a3f8...bc.wal            # um arquivo por Job ID
│   └── ...
└── dlq/                          # dead letter queue (falhas permanentes)
    ├── 9e2c...7a.wal
    └── ...
```

Caminho configurável via `CHATCLI_QUALITY_REFLEXION_QUEUE_BASE_DIR` (default: `<workspace>/.chatcli/reflexion`).

<Tip>Operadores podem `ls` o diretório pra triagem rápida sem tools especiais. Cada record é um JSON dentro do framing binário — `xxd` + o protocolo na doc do `lessonq` ajudam em forense.</Tip>

***

## Protocolo do lesson generator

O system prompt instrui o modelo a ser **geral**, não one-off:

```text theme={"system"}
Rules:
- A "lesson" must be GENERAL enough to apply next time a similar task
  comes up — not one-off and not a play-by-play.
- If there is genuinely nothing to learn (e.g. the task was trivial and
  the failure was a transient network blip), reply with exactly:
  <skip>nothing actionable</skip>
- Otherwise emit ALL of the following blocks. Keep each to ONE line.
- "tags" is a comma-separated list of 2-5 short keywords (lowercase,
  hyphenated if needed) that future similar tasks will likely contain.

OUTPUT:
<situation>brief description of when this lesson applies</situation>
<mistake>what went wrong this time</mistake>
<correction>what to do differently next time</correction>
<tags>tag1, tag2, tag3</tags>
```

<Tip>O bloco `<skip>` existe justamente para evitar pollution da memória com "lições" de falhas transientes. O modelo pode recusar gerar lição com custo zero de persistência.</Tip>

***

## `/reflect` — caminho manual sem LLM

Quando você sabe a lição e não precisa do LLM destilando:

```bash theme={"system"}
/reflect quando editar arquivos Go grandes use Edit, não rewrite total
```

Isso entra direto em `memory.Fact`:

```text theme={"system"}
LESSON: quando editar arquivos Go grandes use Edit, não rewrite total
MISTAKE: (user-supplied lesson; no automatic mistake detection)
CORRECTION: quando editar arquivos Go grandes use Edit, não rewrite total
TRIGGER: manual
```

Tags geradas: `["reflexion", "trigger:manual", "user-supplied"]`.

<Warning>O caminho manual **não faz** chamada LLM — é barato, síncrono e ideal para capturar aprendizados durante a sessão.</Warning>

***

## Como a lição "volta"

Uma vez persistida, a lesson é um fact normal no índice. Ela emerge via:

1. **Retrieval por hints**: se a próxima task mencionar keywords em `Tags`, o scorer relevance-based a surfaceia.
2. **HyDE amplifica**: com `CHATCLI_QUALITY_HYDE_ENABLED=true`, a hipótese gerada cobre conceitos semelhantes, aumentando chance de match.
3. **Vector search**: com embeddings configurados, a lesson é buscada por proximidade cosseno.

O system prompt do turn seguinte contém a seção `## Long-term Memory` com o texto da lesson, e o modelo tem todas as pistas para **não repetir o erro**.

***

## Variáveis de ambiente

### Gates (quando disparar)

| Env var                                      | Default | O que faz                                  |
| -------------------------------------------- | ------- | ------------------------------------------ |
| `CHATCLI_QUALITY_REFLEXION_ENABLED`          | `true`  | Master switch                              |
| `CHATCLI_QUALITY_REFLEXION_ON_ERROR`         | `true`  | Disparar em erro de tool                   |
| `CHATCLI_QUALITY_REFLEXION_ON_HALLUCINATION` | `true`  | Disparar em `verified_with_discrepancy`    |
| `CHATCLI_QUALITY_REFLEXION_ON_LOW_QUALITY`   | `false` | Disparar em `refine_low_quality`           |
| `CHATCLI_QUALITY_REFLEXION_PERSIST`          | `true`  | Escrever em memory.Fact (false = log-only) |

### Fila durável (WAL + worker pool + DLQ)

| Env var                                         | Default                          | Efeito                                                                   |
| ----------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------ |
| `CHATCLI_QUALITY_REFLEXION_QUEUE_ENABLED`       | `true`                           | Master switch da fila. `false` volta ao modo legado (detached goroutine) |
| `CHATCLI_QUALITY_REFLEXION_QUEUE_WORKERS`       | `2`                              | Tamanho do worker pool. Reflexion é I/O-bound na chamada LLM             |
| `CHATCLI_QUALITY_REFLEXION_QUEUE_CAPACITY`      | `1000`                           | Profundidade máxima em memory antes de aplicar overflow policy           |
| `CHATCLI_QUALITY_REFLEXION_QUEUE_DROP_OLDEST`   | `false`                          | Overflow policy: `true` = drop oldest; `false` = block com timeout       |
| `CHATCLI_QUALITY_REFLEXION_QUEUE_BLOCK_TIMEOUT` | `5s`                             | Quanto Enqueue espera quando fila está cheia (se `DROP_OLDEST=false`)    |
| `CHATCLI_QUALITY_REFLEXION_QUEUE_MAX_ATTEMPTS`  | `5`                              | Retries totais por job antes de mover pra DLQ                            |
| `CHATCLI_QUALITY_REFLEXION_QUEUE_INITIAL_DELAY` | `1s`                             | Primeiro delay de retry                                                  |
| `CHATCLI_QUALITY_REFLEXION_QUEUE_MAX_DELAY`     | `5m`                             | Cap no retry exponencial                                                 |
| `CHATCLI_QUALITY_REFLEXION_QUEUE_JITTER`        | `0.2`                            | Jitter fracionário (\[0, 0.5]) — full jitter AWS-style                   |
| `CHATCLI_QUALITY_REFLEXION_QUEUE_JOB_TIMEOUT`   | `2m`                             | Timeout por chamada ao processor (LLM + persist)                         |
| `CHATCLI_QUALITY_REFLEXION_QUEUE_STALE_AFTER`   | `168h`                           | Records do WAL mais velhos que isso são descartados no replay (7 dias)   |
| `CHATCLI_QUALITY_REFLEXION_QUEUE_BASE_DIR`      | `<workspace>/.chatcli/reflexion` | Override do diretório raiz (WAL + DLQ)                                   |

### Métricas Prometheus

A fila emite 10 métricas em `chatcli_lessonq_*`:

| Métrica                       | Tipo      | Labels    | Significado                                        |
| ----------------------------- | --------- | --------- | -------------------------------------------------- |
| `enqueue_total`               | Counter   | `outcome` | accepted, rejected\_full, deduped, dropped\_oldest |
| `queue_depth`                 | Gauge     | —         | Pendentes in-memory                                |
| `processing_duration_seconds` | Histogram | `outcome` | Tempo dequeue→outcome                              |
| `attempts_total`              | Counter   | `outcome` | success, skipped, transient, permanent             |
| `retry_total`                 | Counter   | `attempt` | Retries por número da tentativa                    |
| `dlq_size`                    | Gauge     | —         | Jobs na DLQ                                        |
| `wal_segments`                | Gauge     | —         | Arquivos `.wal` no diretório ativo                 |
| `wal_corruption_total`        | Counter   | —         | Records rejeitados por CRC mismatch/torn write     |
| `stale_discarded_total`       | Counter   | —         | Records descartados no replay por idade            |
| `persist_failures_total`      | Counter   | —         | Falhas no callback de memory.Fact                  |

***

## Exemplo de ciclo completo

<Steps>
  <Step title="Usuário pede task que falha">
    `/coder refactor pkg/engine to extract Close method`
  </Step>

  <Step title="CoderAgent tenta rewrite total">
    Arquivo tem 2000 linhas, provider responde com timeout.
  </Step>

  <Step title="PostRun detecta result.Error != nil">
    OnError trigger matched.
  </Step>

  <Step title="goroutine: GenerateLesson">
    Model emite:

    ```
    <situation>Refactoring large Go files (>1000 lines)</situation>
    <mistake>Attempted full rewrite via @coder write</mistake>
    <correction>Use @coder patch or Edit tool for surgical changes</correction>
    <tags>go, refactor, large-file, edit-tool</tags>
    ```
  </Step>

  <Step title="Persiste em memory.Fact">
    Categoria=`lesson`, workspace=current project.
  </Step>

  <Step title="Próxima semana, usuário pede refactor similar">
    `/coder refactor pkg/auth/manager.go split into smaller files`
  </Step>

  <Step title="RAG+HyDE traz a lesson">
    Tags `refactor` + `large-file` matchem. Lesson aparece no system prompt.
  </Step>

  <Step title="Coder escolhe abordagem correta de primeira">
    Emite múltiplos `@coder patch` ao invés de `write`. Task concluída sem timeout.
  </Step>
</Steps>

***

## Inspecionar lições armazenadas

```bash theme={"system"}
# Lições já persistidas (materializadas em memory.Fact)
/memory longterm | grep -A3 "^LESSON:"
cat ~/.chatcli/memory/memory_index.json | jq '.[] | select(.category=="lesson")'

# Fila durável — pendentes + DLQ em tempo real
/reflect list               # pendentes + DLQ
/reflect failed             # só DLQ (triagem)
/config quality             # estado dos hooks + queue depth + dlq size
```

### Prometheus snapshots úteis

```promql theme={"system"}
# Saúde geral da fila
chatcli_lessonq_queue_depth
chatcli_lessonq_dlq_size
sum(rate(chatcli_lessonq_attempts_total[5m])) by (outcome)

# Detecção de regressão: DLQ crescendo sem novos success
rate(chatcli_lessonq_attempts_total{outcome="permanent"}[5m]) > 0

# Alertar em corrupção do WAL (sinal de fs instável)
increase(chatcli_lessonq_wal_corruption_total[1h]) > 0

# Latência percentil do processor
histogram_quantile(0.95,
  rate(chatcli_lessonq_processing_duration_seconds_bucket[5m]))
```

***

## Leia também

<CardGroup cols={2}>
  <Card title="#4 RAG + HyDE" icon="database" href="/pt/features/quality/rag-hyde">
    Como as lições são recuperadas em tarefas futuras via retrieval semântico.
  </Card>

  <Card title="#6 CoVe" icon="check-double" href="/pt/features/quality/cove">
    O verifier gera o signal `verified_with_discrepancy` que Reflexion consome.
  </Card>

  <Card title="Bootstrap Memory" icon="brain" href="/pt/features/bootstrap-memory">
    Como a memória de longo prazo foi estruturada pré-pipeline.
  </Card>

  <Card title="Memory Commands" icon="terminal" href="/pt/core-concepts/context-commands">
    `/memory load`, `/memory show`, `/memory longterm`.
  </Card>

  <Card title="Configuração quality" icon="sliders" href="/pt/features/quality/configuration">
    Todos os `CHATCLI_QUALITY_REFLEXION_QUEUE_*` + presets.
  </Card>
</CardGroup>
