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
1
Worker finishes
Any agent (CoderAgent, FileAgent, β¦) produces
result.Output.2
RefineHook.PostRun is invoked
3
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
4
Replace result.Output
result.Output = currentDraft (only if it changed).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
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
Strict is for high-stakes workloads (legal contracts, security code) where a false-positive convergence = shipping unpolished output.
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:
Add more via env:
/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
Semantic convergence cascade
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
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.