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
convergedRefine is a cheap check that detects when two consecutive revisions are “practically identical”:
EpsilonChars=50 (default), the loop stops when two passes differ by less than 50 chars — clear sign of stabilization. This avoids spending tokens iterating on an output the model already considers ready.
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
| 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 | Convergence threshold in chars |
CHATCLI_QUALITY_REFINE_EXCLUDE | formatter,deps,refiner,verifier | CSV of agents that are NOT refined |
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.