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.
O que é uma Lesson
UmaLesson é um registro de quatro linhas:
memory.Fact, o Content fica:
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
- OnError
- OnHallucination
- OnLowQuality
- Manual via /reflect
Error != nil. Exemplos: timeout, tool call inválido, crash do provedor.
Default: ON.Fluxo — modo durável (default)
ComCHATCLI_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:
PostRun inspeciona o trigger
ReflexionHook.PostRun(ctx, hc, result) olha result.Metadata + result.Error — se nenhum gate bate, retorna em μs.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.Retorno imediato ao pipeline
PostRun retorna nil; o turn do usuário continua sem espera. A latência adicional é o fsync (tipicamente < 1 ms).
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>.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.
Fallback: modo legado (goroutine detached)
SeCHATCLI_QUALITY_REFLEXION_QUEUE_ENABLED=false, o hook volta ao comportamento original:
Fila Durável — WAL + Worker Pool + DLQ
A fila é implementada emcli/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:
- CRC duplo detecta torn writes (crash no meio do fsync). Records corruptos são descartados no replay +
chatcli_lessonq_wal_corruption_totalincrementa. - Atomic rename: escrita em
<id>.tmp.<pid>.<seq>→ fsync → rename → dir fsync. Nunca um leitor vê record parcial. - O(1) ACK: um único
unlinkremove o record. Sem compactação em background.
Worker Pool
- 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:
Retry com Jitter
Transient errors (ctx timeout, provider 429/503, temp fs error) viram reschedule: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):
- Queue fecha — sem novos dequeues.
- Workers terminam in-flight (ou são cancelados no timeout).
- WAL/DLQ fecham.
/reflect — Comandos
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.Arquivos e layout
CHATCLI_QUALITY_REFLEXION_QUEUE_BASE_DIR (default: <workspace>/.chatcli/reflexion).
Protocolo do lesson generator
O system prompt instrui o modelo a ser geral, não one-off:/reflect — caminho manual sem LLM
Quando você sabe a lição e não precisa do LLM destilando:
memory.Fact:
["reflexion", "trigger:manual", "user-supplied"].
Como a lição “volta”
Uma vez persistida, a lesson é um fact normal no índice. Ela emerge via:- Retrieval por hints: se a próxima task mencionar keywords em
Tags, o scorer relevance-based a surfaceia. - HyDE amplifica: com
CHATCLI_QUALITY_HYDE_ENABLED=true, a hipótese gerada cobre conceitos semelhantes, aumentando chance de match. - Vector search: com embeddings configurados, a lesson é buscada por proximidade cosseno.
## 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 emchatcli_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
Próxima semana, usuário pede refactor similar
/coder refactor pkg/auth/manager.go split into smaller filesInspecionar lições armazenadas
Prometheus snapshots úteis
Leia também
#4 RAG + HyDE
Como as lições são recuperadas em tarefas futuras via retrieval semântico.
#6 CoVe
O verifier gera o signal
verified_with_discrepancy que Reflexion consome.Bootstrap Memory
Como a memória de longo prazo foi estruturada pré-pipeline.
Memory Commands
/memory load, /memory show, /memory longterm.Configuração quality
Todos os
CHATCLI_QUALITY_REFLEXION_QUEUE_* + presets.