Pular para o conteúdo principal
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.
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.
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.

O que é uma Lesson

Uma Lesson é um registro de quatro linhas:
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:
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

if cfg.OnError && result.Error != nil {
    return "error"
}
O worker retornou Error != nil. Exemplos: timeout, tool call inválido, crash do provedor. Default: ON.

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:
1

PostRun inspeciona o trigger

ReflexionHook.PostRun(ctx, hc, result) olha result.Metadata + result.Error — se nenhum gate bate, retorna em μs.
2

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.
3

Retorno imediato ao pipeline

PostRun retorna nil; o turn do usuário continua sem espera. A latência adicional é o fsync (tipicamente < 1 ms).
4

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>.
5

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.
6

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).
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.

Fallback: modo legado (goroutine detached)

Se CHATCLI_QUALITY_REFLEXION_QUEUE_ENABLED=false, o hook volta ao comportamento original:
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:
[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

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:
/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:
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

/reflect                     # queue depth + DLQ size + subcommands hint
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/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).
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.

Protocolo do lesson generator

O system prompt instrui o modelo a ser geral, não one-off:
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>
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.

/reflect — caminho manual sem LLM

Quando você sabe a lição e não precisa do LLM destilando:
/reflect quando editar arquivos Go grandes use Edit, não rewrite total
Isso entra direto em memory.Fact:
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"].
O caminho manual não faz chamada LLM — é barato, síncrono e ideal para capturar aprendizados durante a sessão.

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 varDefaultO que faz
CHATCLI_QUALITY_REFLEXION_ENABLEDtrueMaster switch
CHATCLI_QUALITY_REFLEXION_ON_ERRORtrueDisparar em erro de tool
CHATCLI_QUALITY_REFLEXION_ON_HALLUCINATIONtrueDisparar em verified_with_discrepancy
CHATCLI_QUALITY_REFLEXION_ON_LOW_QUALITYfalseDisparar em refine_low_quality
CHATCLI_QUALITY_REFLEXION_PERSISTtrueEscrever em memory.Fact (false = log-only)

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

Env varDefaultEfeito
CHATCLI_QUALITY_REFLEXION_QUEUE_ENABLEDtrueMaster switch da fila. false volta ao modo legado (detached goroutine)
CHATCLI_QUALITY_REFLEXION_QUEUE_WORKERS2Tamanho do worker pool. Reflexion é I/O-bound na chamada LLM
CHATCLI_QUALITY_REFLEXION_QUEUE_CAPACITY1000Profundidade máxima em memory antes de aplicar overflow policy
CHATCLI_QUALITY_REFLEXION_QUEUE_DROP_OLDESTfalseOverflow policy: true = drop oldest; false = block com timeout
CHATCLI_QUALITY_REFLEXION_QUEUE_BLOCK_TIMEOUT5sQuanto Enqueue espera quando fila está cheia (se DROP_OLDEST=false)
CHATCLI_QUALITY_REFLEXION_QUEUE_MAX_ATTEMPTS5Retries totais por job antes de mover pra DLQ
CHATCLI_QUALITY_REFLEXION_QUEUE_INITIAL_DELAY1sPrimeiro delay de retry
CHATCLI_QUALITY_REFLEXION_QUEUE_MAX_DELAY5mCap no retry exponencial
CHATCLI_QUALITY_REFLEXION_QUEUE_JITTER0.2Jitter fracionário ([0, 0.5]) — full jitter AWS-style
CHATCLI_QUALITY_REFLEXION_QUEUE_JOB_TIMEOUT2mTimeout por chamada ao processor (LLM + persist)
CHATCLI_QUALITY_REFLEXION_QUEUE_STALE_AFTER168hRecords do WAL mais velhos que isso são descartados no replay (7 dias)
CHATCLI_QUALITY_REFLEXION_QUEUE_BASE_DIR<workspace>/.chatcli/reflexionOverride do diretório raiz (WAL + DLQ)

Métricas Prometheus

A fila emite 10 métricas em chatcli_lessonq_*:
MétricaTipoLabelsSignificado
enqueue_totalCounteroutcomeaccepted, rejected_full, deduped, dropped_oldest
queue_depthGaugePendentes in-memory
processing_duration_secondsHistogramoutcomeTempo dequeue→outcome
attempts_totalCounteroutcomesuccess, skipped, transient, permanent
retry_totalCounterattemptRetries por número da tentativa
dlq_sizeGaugeJobs na DLQ
wal_segmentsGaugeArquivos .wal no diretório ativo
wal_corruption_totalCounterRecords rejeitados por CRC mismatch/torn write
stale_discarded_totalCounterRecords descartados no replay por idade
persist_failures_totalCounterFalhas no callback de memory.Fact

Exemplo de ciclo completo

1

Usuário pede task que falha

/coder refactor pkg/engine to extract Close method
2

CoderAgent tenta rewrite total

Arquivo tem 2000 linhas, provider responde com timeout.
3

PostRun detecta result.Error != nil

OnError trigger matched.
4

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>
5

Persiste em memory.Fact

Categoria=lesson, workspace=current project.
6

Próxima semana, usuário pede refactor similar

/coder refactor pkg/auth/manager.go split into smaller files
7

RAG+HyDE traz a lesson

Tags refactor + large-file matchem. Lesson aparece no system prompt.
8

Coder escolhe abordagem correta de primeira

Emite múltiplos @coder patch ao invés de write. Task concluída sem timeout.

Inspecionar lições armazenadas

# 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

# 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

#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.