RefinerAgent is a pure-reasoning worker (zero tool access) that operates on text only, and can be invoked directly by the orchestrator or automatically by RefineHook as post-processing.
Self-Refine is opt-in. With
CHATCLI_QUALITY_REFINE_ENABLED=false (default), the RefineHook is not added to the pipeline and zero overhead is introduced.RefinerAgent protocol
The model receives TASK + DRAFT and emits two XML-ish blocks:- Output ONLY the two blocks — nothing before, nothing after.
- If draft is already excellent,
<revised>repeats verbatim and<critique>says “no material issues”. - Never invents new requirements beyond the TASK.
- Keeps the draft’s format (code → code, prose → prose).
RefineHook flow
Multi-pass loop
For each pass up to
MaxPasses:- Dispatch
workers.AgentCall{Agent: refiner, Task: RefineDirective + "Task: ...\n\nDraft: ..."} - Receives
res.Outputwith contents of<revised> - If
convergedRefine(currentDraft, res.Output, EpsilonChars)→ break
Convergence — semantic cascade
Self-Refine uses a char → Jaccard → embedding cascade to decide when to stop. The old char-level heuristic (convergedCharHeuristic, still available as fallback) only caught literal equality; the cascade catches same meaning in different words, which is the real case for most rewrites.
The three scorers
| Scorer | Cost | Catches | Confidence |
|---|---|---|---|
| Char | μs | Literal equality, length delta | High at extremes, low in the middle |
| Jaccard | ms | Normalized token sets (lowercase, EN/PT stop-words, no punctuation) — captures reordering | Grows with corpus size |
| Embedding | 100-500ms + $ | Cosine similarity via embedding.Provider (Voyage/OpenAI) — captures paraphrase + synonyms | High, authoritative |
Quality regression guard
Beyond detecting convergence, the hook compares each new revision against the original draft. If similarity starts dropping between passes (rewrite is drifting worse), it reverts to the best draft seen and setsrefine_rolled_back=true metadata — a signal Reflexion consumes:
LRU cache with TTL
Embedding is expensive ($). The cascade caches vectors bysha256(text) in a bounded LRU (default 256 entries, 5min TTL) — during a multi-pass loop, the same string appears in consecutive comparisons and hits skip duplicate calls.
Per-scorer circuit breaker
If the embedder returns 3 consecutive errors (provider 429/503/timeout), the breaker opens for 30s — the cascade gracefully degrades to Jaccard withDegradedFrom="embedding_unavailable" in the Score. Zero refine stall.
Strict vs permissive mode
| Mode | Embedder down | Behavior |
|---|---|---|
| permissive (default) | Degrades to Jaccard | Marks DegradedFrom in the trail, continues |
| strict | Refuses convergence | Treats embedding as authoritative; without it, Converged=false |
Legacy fallback
When no checker is wired (orCONVERGENCE_ENABLED=false), falls back to the original char-level heuristic:
EpsilonChars=50 (default), stops when two passes differ by less than 50 chars. Cheap but blind to paraphrase — hence the cascade as enterprise default.
Exclude lists (anti-recursion and mechanical agents)
DefaultExcludeAgents:
| Agent | Reason |
|---|---|
| formatter | Mechanical output (formatted file), refine doesn’t add value |
| deps | Output is deterministic interpretation of go list, npm ls, etc. |
| refiner | Anti-recursion: refining the refiner’s output creates an infinite loop |
| verifier | Same reason — verifier already delivers polished output |
/refine — session toggle
Instead of editing env vars and restarting, use the slash:
cli.qualityOverrides.Refine as *bool:
nil→ defer to/config quality&true→ force on&false→ force off
AgentMode.Run() builds its qualityConfig.
Environment variables
Basics
| Env var | Default | What it does |
|---|---|---|
CHATCLI_QUALITY_REFINE_ENABLED | false | Master switch |
CHATCLI_QUALITY_REFINE_MAX_PASSES | 1 | Hard cap on passes (recommend 1-2) |
CHATCLI_QUALITY_REFINE_MIN_BYTES | 200 | Don’t refine outputs smaller than this |
CHATCLI_QUALITY_REFINE_EPSILON | 50 | Char-level fallback threshold |
CHATCLI_QUALITY_REFINE_EXCLUDE | formatter,deps,refiner,verifier | CSV of agents that are NOT refined |
Semantic convergence cascade
| Env var | Default | Effect |
|---|---|---|
CHATCLI_QUALITY_REFINE_CONVERGENCE_ENABLED | true | Cascade master switch. false = char heuristic only |
CHATCLI_QUALITY_REFINE_CONVERGENCE_EMBEDDING | false | Include embedding scorer — opt-in because it costs $ |
CHATCLI_QUALITY_REFINE_CONVERGENCE_STRICT | false | Strict mode: refuse convergence without embedding |
CHATCLI_QUALITY_REFINE_CONVERGENCE_CHAR_HIGH | 0.99 | Sim ≥ X on char → short-circuit CONVERGED |
CHATCLI_QUALITY_REFINE_CONVERGENCE_CHAR_LOW | 0.3 | Sim < X on char → short-circuit DIVERGED |
CHATCLI_QUALITY_REFINE_CONVERGENCE_JACCARD_HIGH | 0.95 | Sim ≥ X on Jaccard (confidence ≥ 0.6) → CONVERGED |
CHATCLI_QUALITY_REFINE_CONVERGENCE_EMBEDDING_SIM | 0.92 | Final embedding cosine threshold |
CHATCLI_QUALITY_REFINE_CONVERGENCE_CACHE_SIZE | 256 | LRU cache size |
CHATCLI_QUALITY_REFINE_CONVERGENCE_CACHE_TTL_MIN | 5 | Cache TTL in minutes |
CHATCLI_QUALITY_REFINE_CONVERGENCE_BREAKER_THRESHOLD | 3 | Consecutive embedder failures before breaker opens |
Refiner agent override
RefinerAgent has defaults model="" and effort="medium". Override:
Example: refine on documentation response
- Draft (CoderAgent output)
- Critique
- Revised
Direct invocation by the orchestrator
Beyond the automatic hook, the orchestrator can call the refiner via<agent_call>:
- You want to refine a specific output without enabling the global hook.
- The task itself is “improve this text”.
- Complex chains: coder writes → reviewer analyzes → refiner polishes the analysis before delivery.
Cost and latency
| Config | Extra LLM calls per turn | Typical latency |
|---|---|---|
MaxPasses=1 (default) | +1 | 1-3s with Haiku, 3-8s with Sonnet |
MaxPasses=2 | +1 to +2 (convergence may stop at 1) | up to 2x |
MaxPasses=3+ | Rarely converges → expensive | Avoid |
See also
#6 CoVe
Chain-of-Verification complements refine with factual checking.
#3 Reflexion
If refine flags low-quality across multiple turns, Reflexion persists the lesson.
ReviewerAgent
Pre-pipeline agent that does code review. Refiner is complementary: one analyzes, the other rewrites.
Configuration
All CHATCLI_QUALITY_REFINE_* in one place.