feat(agent): improve LLM feedback loop and convergence detection

Three related improvements to help the model learn and explore effectively:

Strategy JSON in history: include the compact strategy JSON in each
IterationRecord::summary() so the LLM knows exactly what was tested in
every past iteration, not just the outcome metrics. Without this the model
had no record of what it tried once conversation history was trimmed.

Rule comment in audit: include rule_comment from the condition audit in
the formatted audit string so the LLM can correlate hit-rate data with
the rule's stated purpose.

Convergence detection and anti-anchoring: diagnose_history() now returns
(String, bool) where the bool signals that the last 3 iterations had
avg_sharpe spread < 0.03 (model stuck in local optimum). When converged:
- Emit a ⚠ CONVERGENCE DETECTED note listing untried candle intervals
- Suppress best_so_far JSON to break the anchoring effect that was
  causing the model to produce near-identical strategies for 13+ iterations
- Targeted "try a different approach" instruction

Also add volume-as-field clarification to the DSL mistakes section in
the system prompt, fixing the "unknown variant `volume`" submit error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 14:38:07 +02:00
parent fb1145acae
commit e27aabae34
3 changed files with 206 additions and 8 deletions

View File

@@ -165,6 +165,9 @@ fn format_audit_summary(audit: &Value) -> String {
.map(|rule| {
let idx = rule["rule_index"].as_u64().unwrap_or(0);
let fired = rule["times_fired"].as_u64().unwrap_or(0);
// Include the rule comment so the LLM knows which rule is which.
let comment = rule["rule_comment"].as_str().unwrap_or("");
let comment_part = if comment.is_empty() { String::new() } else { format!(" \"{comment}\"") };
let cond_summary = rule["conditions"]
.as_array()
.map(|conds| {
@@ -185,7 +188,7 @@ fn format_audit_summary(audit: &Value) -> String {
.join(" ")
})
.unwrap_or_default();
format!("R{idx}(f={fired})[{cond_summary}]")
format!("R{idx}(f={fired}){comment_part}[{cond_summary}]")
})
.collect();
return parts.join(" | ");