Skip to main content
Self-Refine turns raw worker output into polished versions via a deliberate critique β†’ rewrite cycle. The 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:
System prompt rules:
  • 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).
The parser is tolerant: if the model violates the protocol, RefinerAgent.Execute returns the full response as output β€” Reflexion picks up the violation as a low-quality signal.

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.Output with 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 sets refine_rolled_back=true metadata β€” a signal Reflexion consumes:
This prevents the model from iterating toward a worse answer when critique over-interprets.

LRU cache with TTL

Embedding is expensive ($). The cascade caches vectors by sha256(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 with DegradedFrom="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 (or CONVERGENCE_ENABLED=false), falls back to the original char-level heuristic:
With 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)

Default ExcludeAgents:
Add more via env:

/refine β€” session toggle

Instead of editing env vars and restarting, use the slash:
The session override lives in cli.qualityOverrides.Refine as *bool:
  • nil β†’ defer to /config quality
  • &true β†’ force on
  • &false β†’ force off
It’s applied on top of env when AgentMode.Run() builds its qualityConfig.

Environment variables

Basics

Semantic convergence cascade

To enable embedding: CHATCLI_QUALITY_REFINE_CONVERGENCE_EMBEDDING=1 + any embeddings provider (CHATCLI_EMBED_PROVIDER=voyage / openai / bedrock). The cascade reuses the same provider as HyDE β€” zero duplicate infra. See RAG + HyDE for per-provider details.

Refiner agent override

RefinerAgent has defaults model="" and effort="medium". Override:

Example: refine on documentation response

Quality judgment: vague, no example, dismissive β€œjust”.

Direct invocation by the orchestrator

Beyond the automatic hook, the orchestrator can call the refiner via <agent_call>:
Useful when:
  • 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

Refine shines in writing tasks: documentation, summaries, reports. Less useful in tool-heavy workflows (shell, git, file) because outputs are already mechanical.

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.