梦兽编程
AI_SUITE

Loop Workflow Deep Dive: Iterative Optimization, Termination Conditions, and Convergence Guarantees

In-depth analysis of the ADK Go Loop workflow iteration mechanism, termination condition design, convergence analysis, state evolution, and production-grade loop control strategies.

The Loop workflow is the core pattern in an Agent Team for handling iterative optimization tasks. Unlike Sequential and Parallel workflows, which execute once, the Loop workflow uses a loop-evaluate-optimize closed-loop mechanism that lets an Agent autonomously improve output quality. However, looping brings risks—infinite loops, convergence oscillation, and state bloat—which require rigorous engineering controls. This post dives into the mathematical model, termination theory, state evolution, and production practice of Loop workflows.

Mathematical Model of Iterative Optimization

Convergence Analysis

A Loop workflow can be formalized as an iterative function system:

x_{n+1} = f(x_n, e_n)

Where:

  • x_n: output of the nth iteration
  • f: Agent improvement function
  • e_n: evaluation feedback of the nth iteration

Convergence condition: when there exists a quality metric Q(x) such that Q(x_{n+1}) >= Q(x_n) holds for all n, and Q(x) has an upper bound, then the sequence {x_n} must converge.

// Convergence analyzer
type ConvergenceAnalyzer struct {
    qualityHistory []float64
    windowSize     int
    threshold      float64
}

func (ca *ConvergenceAnalyzer) IsConverged() (bool, float64) {
    if len(ca.qualityHistory) < ca.windowSize {
        return false, 0
    }

    // Take the most recent windowSize quality values
    recent := ca.qualityHistory[len(ca.qualityHistory)-ca.windowSize:]

    // Calculate variance
    mean := calculateMean(recent)
    variance := calculateVariance(recent, mean)

    // Variance below threshold means converged
    converged := variance < ca.threshold

    return converged, variance
}

func (ca *ConvergenceAnalyzer) DetectOscillation() bool {
    if len(ca.qualityHistory) < 4 {
        return false
    }

    // Detect oscillation: quality values repeatedly rise and fall
    last4 := ca.qualityHistory[len(ca.qualityHistory)-4:]

    upDown := (last4[1] > last4[0]) && (last4[2] < last4[1]) && (last4[3] > last4[2])
    downUp := (last4[1] < last4[0]) && (last4[2] > last4[1]) && (last4[3] < last4[2])

    return upDown || downUp
}

Quality Metric Design

// Multi-dimensional quality evaluation system
type QualityMetrics struct {
    // Content quality (0-1)
    Relevance    float64 // relevance
    Coherence    float64 // coherence
    Completeness float64 // completeness
    Accuracy     float64 // accuracy

    // Structural quality (0-1)
    Structure  float64 // structural soundness
    Formatting float64 // formatting compliance

    // Language quality (0-1)
    Grammar float64 // grammatical correctness
    Style   float64 // style consistency

    // Business quality (0-1)
    Requirements float64 // requirement satisfaction
    Constraints  float64 // constraint satisfaction
}

func (qm *QualityMetrics) OverallScore() float64 {
    // Weighted combined score
    weights := map[string]float64{
        "Relevance":    0.20,
        "Coherence":    0.15,
        "Completeness": 0.15,
        "Accuracy":     0.20,
        "Structure":    0.10,
        "Formatting":   0.05,
        "Grammar":      0.05,
        "Style":        0.05,
        "Requirements": 0.03,
        "Constraints":  0.02,
    }

    score := 0.0
    score += weights["Relevance"] * qm.Relevance
    score += weights["Coherence"] * qm.Coherence
    score += weights["Completeness"] * qm.Completeness
    score += weights["Accuracy"] * qm.Accuracy
    score += weights["Structure"] * qm.Structure
    score += weights["Formatting"] * qm.Formatting
    score += weights["Grammar"] * qm.Grammar
    score += weights["Style"] * qm.Style
    score += weights["Requirements"] * qm.Requirements
    score += weights["Constraints"] * qm.Constraints

    return score
}

// Evaluate quality via LLM
func evaluateWithLLM(ctx context.Context, model agent.Model, content string, criteria []string) (*QualityMetrics, error) {
    prompt := fmt.Sprintf(`Please evaluate the quality of the following content, scoring each dimension (0-1):

%s

Evaluation dimensions:
%s

Please output the scores in JSON format.`, content, strings.Join(criteria, "\n"))

    response, err := model.GenerateContent(ctx, prompt)
    if err != nil {
        return nil, err
    }

    var metrics QualityMetrics
    if err := json.Unmarshal([]byte(response), &metrics); err != nil {
        return nil, err
    }

    return &metrics, nil
}

Loop Workflow Architecture Design

Core Components

type LoopWorkflow struct {
    agent           *agent.Agent        // iteration Agent
    evaluator       Evaluator           // quality evaluator
    exitCondition   ExitCondition       // exit condition
    maxIterations   int                 // maximum iterations
    minIterations   int                 // minimum iterations
    convergenceCfg  *ConvergenceConfig  // convergence config
    stateManager    StateManager        // state manager
    feedbackBuilder FeedbackBuilder     // feedback builder
    strategy        IterationStrategy   // iteration strategy
}

// Evaluator interface
type Evaluator interface {
    Evaluate(ctx context.Context, output string, target string) (*EvaluationResult, error)
}

// Exit condition interface
type ExitCondition interface {
    ShouldExit(state *LoopState) (bool, string)
}

// Iteration state
type LoopState struct {
    Iteration      int                    // current iteration count
    CurrentOutput  string                 // current output
    PreviousOutput string                 // previous output
    QualityHistory []float64              // quality history
    BestOutput     string                 // best output
    BestQuality    float64                // best quality
    Feedback       string                 // current feedback
    Context        map[string]interface{} // context state
    StartTime      time.Time              // start time
    TokenUsed      int                    // token consumption
}

// Evaluation result
type EvaluationResult struct {
    Quality  float64         // quality score
    Feedback string          // improvement feedback
    Metrics  *QualityMetrics // detailed metrics
    Passed   bool            // passed
    Criteria map[string]bool // per-criterion pass status
}

Execution Engine

func (lw *LoopWorkflow) Execute(ctx context.Context, input string) (*LoopResult, error) {
    // 1. Initialize state
    state := &LoopState{
        Iteration:   0,
        Context:     make(map[string]interface{}),
        StartTime:   time.Now(),
        BestQuality: -1,
    }

    state.Context["original_input"] = input

    // 2. Initial execution
    output, err := lw.agent.Run(ctx, input)
    if err != nil {
        return nil, fmt.Errorf("initial execution failed: %w", err)
    }

    state.CurrentOutput = output
    state.BestOutput = output

    // 3. Evaluate initial output
    eval, err := lw.evaluator.Evaluate(ctx, output, input)
    if err != nil {
        return nil, fmt.Errorf("initial evaluation failed: %w", err)
    }

    state.QualityHistory = append(state.QualityHistory, eval.Quality)
    state.BestQuality = eval.Quality
    state.Feedback = eval.Feedback

    // 4. Iterative optimization loop
    for {
        state.Iteration++

        // Check exit condition
        shouldExit, reason := lw.exitCondition.ShouldExit(state)
        if shouldExit {
            return lw.buildResult(state, reason), nil
        }

        // Check max iterations
        if state.Iteration >= lw.maxIterations {
            return lw.buildResult(state, "max_iterations_reached"), nil
        }

        // Build iteration input
        iterationInput := lw.feedbackBuilder.Build(state, eval)

        // Execute iteration
        newOutput, err := lw.agent.Run(ctx, iterationInput)
        if err != nil {
            // Iteration failed, use best historical result
            log.Printf("Iteration %d failed: %v", state.Iteration, err)
            return lw.buildResult(state, "iteration_failed"), nil
        }

        state.PreviousOutput = state.CurrentOutput
        state.CurrentOutput = newOutput

        // Evaluate new output
        eval, err = lw.evaluator.Evaluate(ctx, newOutput, input)
        if err != nil {
            log.Printf("Evaluation failed at iteration %d: %v", state.Iteration, err)
            continue
        }

        state.QualityHistory = append(state.QualityHistory, eval.Quality)
        state.Feedback = eval.Feedback
        state.TokenUsed += estimateTokens(newOutput)

        // Update best result
        if eval.Quality > state.BestQuality {
            state.BestQuality = eval.Quality
            state.BestOutput = newOutput
        }

        // Detect convergence
        if lw.convergenceCfg != nil {
            analyzer := &ConvergenceAnalyzer{
                qualityHistory: state.QualityHistory,
                windowSize:     lw.convergenceCfg.WindowSize,
                threshold:      lw.convergenceCfg.Threshold,
            }

            if converged, variance := analyzer.IsConverged(); converged {
                state.Context["convergence_variance"] = variance
                return lw.buildResult(state, "converged"), nil
            }

            if analyzer.DetectOscillation() {
                return lw.buildResult(state, "oscillation_detected"), nil
            }
        }

        // Check token budget
        if lw.tokenBudget != nil && !lw.tokenBudget.CanAllocate(1000) {
            return lw.buildResult(state, "token_budget_exhausted"), nil
        }
    }
}

func (lw *LoopWorkflow) buildResult(state *LoopState, reason string) *LoopResult {
    return &LoopResult{
        Output:         state.BestOutput,
        Quality:        state.BestQuality,
        Iterations:     state.Iteration,
        ExitReason:     reason,
        QualityHistory: state.QualityHistory,
        TokenUsed:      state.TokenUsed,
        Duration:       time.Since(state.StartTime),
    }
}

Termination Condition Design

Composite Exit Conditions

// Combine multiple exit conditions
type CompositeExitCondition struct {
    conditions []ExitCondition
    mode       CompositeMode // ANY | ALL
}

type CompositeMode int
const (
    ModeAny CompositeMode = iota // exit when any condition is met
    ModeAll                       // exit only when all conditions are met
)

func (c *CompositeExitCondition) ShouldExit(state *LoopState) (bool, string) {
    if c.mode == ModeAny {
        for _, cond := range c.conditions {
            if exit, reason := cond.ShouldExit(state); exit {
                return true, reason
            }
        }
        return false, ""
    }

    // ModeAll
    reasons := make([]string, 0)
    for _, cond := range c.conditions {
        exit, reason := cond.ShouldExit(state)
        if !exit {
            return false, ""
        }
        reasons = append(reasons, reason)
    }
    return true, strings.Join(reasons, " + ")
}

// Concrete exit condition implementations

// 1. Quality threshold condition
type QualityThresholdCondition struct {
    Threshold float64
}

func (c *QualityThresholdCondition) ShouldExit(state *LoopState) (bool, string) {
    if state.BestQuality >= c.Threshold {
        return true, fmt.Sprintf("quality_threshold_reached: %.3f >= %.3f",
            state.BestQuality, c.Threshold)
    }
    return false, ""
}

// 2. Maximum iteration condition
type MaxIterationCondition struct {
    MaxIterations int
}

func (c *MaxIterationCondition) ShouldExit(state *LoopState) (bool, string) {
    if state.Iteration >= c.MaxIterations {
        return true, fmt.Sprintf("max_iterations: %d", c.MaxIterations)
    }
    return false, ""
}

// 3. Improvement stall condition
type ImprovementStallCondition struct {
    WindowSize     int
    MinImprovement float64
}

func (c *ImprovementStallCondition) ShouldExit(state *LoopState) (bool, string) {
    if len(state.QualityHistory) < c.WindowSize+1 {
        return false, ""
    }

    recent := state.QualityHistory[len(state.QualityHistory)-c.WindowSize:]
    best := state.QualityHistory[len(state.QualityHistory)-c.WindowSize-1]

    for _, q := range recent {
        if q-best >= c.MinImprovement {
            return false, ""
        }
    }

    return true, fmt.Sprintf("improvement_stalled: no improvement > %.3f in last %d iterations",
        c.MinImprovement, c.WindowSize)
}

// 4. Time budget condition
type TimeBudgetCondition struct {
    MaxDuration time.Duration
}

func (c *TimeBudgetCondition) ShouldExit(state *LoopState) (bool, string) {
    if time.Since(state.StartTime) >= c.MaxDuration {
        return true, fmt.Sprintf("time_budget_exhausted: %v", c.MaxDuration)
    }
    return false, ""
}

// 5. Quality regression condition (prevent over-optimization)
type QualityRegressionCondition struct {
    Threshold float64
}

func (c *QualityRegressionCondition) ShouldExit(state *LoopState) (bool, string) {
    if len(state.QualityHistory) < 2 {
        return false, ""
    }

    last := state.QualityHistory[len(state.QualityHistory)-1]
    prev := state.QualityHistory[len(state.QualityHistory)-2]

    if prev-last > c.Threshold {
        return true, fmt.Sprintf("quality_regression: %.3f -> %.3f", prev, last)
    }
    return false, ""
}

Iteration Strategy Design

Feedback Building Strategy

// Feedback builder interface
type FeedbackBuilder interface {
    Build(state *LoopState, eval *EvaluationResult) string
}

// Detailed feedback builder
type DetailedFeedbackBuilder struct{}

func (b *DetailedFeedbackBuilder) Build(state *LoopState, eval *EvaluationResult) string {
    var feedback strings.Builder

    feedback.WriteString(fmt.Sprintf("Original requirement: %s\n\n", state.Context["original_input"]))
    feedback.WriteString(fmt.Sprintf("Current iteration: %d\n", state.Iteration))
    feedback.WriteString(fmt.Sprintf("Current quality score: %.3f\n", eval.Quality))
    feedback.WriteString(fmt.Sprintf("Historical best: %.3f\n\n", state.BestQuality))

    feedback.WriteString("Evaluation feedback:\n")
    feedback.WriteString(eval.Feedback)
    feedback.WriteString("\n\n")

    // Add concrete improvement suggestions
    feedback.WriteString("Aspects needing improvement:\n")
    for criterion, passed := range eval.Criteria {
        if !passed {
            feedback.WriteString(fmt.Sprintf("- %s: not met\n", criterion))
        }
    }

    feedback.WriteString("\nPlease improve the content based on the above feedback and output the optimized version.")

    return feedback.String()
}

// Diff feedback builder (provides before-and-after comparison)
type DiffFeedbackBuilder struct{}

func (b *DiffFeedbackBuilder) Build(state *LoopState, eval *EvaluationResult) string {
    var feedback strings.Builder

    feedback.WriteString("Please optimize the following content.\n\n")
    feedback.WriteString("[Current Version]\n")
    feedback.WriteString(state.CurrentOutput)
    feedback.WriteString("\n\n")

    if state.PreviousOutput != "" {
        feedback.WriteString("[Previous Version]\n")
        feedback.WriteString(state.PreviousOutput)
        feedback.WriteString("\n\n")

        feedback.WriteString(fmt.Sprintf("Quality change: %.3f -> %.3f\n",
            state.QualityHistory[len(state.QualityHistory)-2], eval.Quality))
    }

    feedback.WriteString("\nImprovement direction: ")
    feedback.WriteString(eval.Feedback)

    return feedback.String()
}

Adaptive Iteration Strategy

// Dynamically adjust iteration strategy based on current state
type AdaptiveIterationStrategy struct {
    baseStrategy   IterationStrategy
    qualityTargets []float64 // stage-based quality targets
    currentPhase   int
}

func (s *AdaptiveIterationStrategy) GetNextInput(state *LoopState, eval *EvaluationResult) string {
    // Select stage based on current quality
    for i, target := range s.qualityTargets {
        if state.BestQuality < target {
            s.currentPhase = i
            break
        }
    }

    // Different strategies for different stages
    switch s.currentPhase {
    case 0:
        // Phase 1: focus on structure and completeness
        return s.buildStructureFocusPrompt(state, eval)
    case 1:
        // Phase 2: focus on content and accuracy
        return s.buildContentFocusPrompt(state, eval)
    case 2:
        // Phase 3: focus on language and style
        return s.buildStyleFocusPrompt(state, eval)
    default:
        return s.baseStrategy.GetNextInput(state, eval)
    }
}

func (s *AdaptiveIterationStrategy) buildStructureFocusPrompt(state *LoopState, eval *EvaluationResult) string {
    return fmt.Sprintf(`Current quality: %.3f, focus on structure optimization.

Current content:
%s

Please optimize the following:
1. Ensure clear headings and paragraph structure.
2. Add appropriate subheadings.
3. Ensure logical flow is smooth.
4. Check the completeness of the beginning and end.

Output the complete optimized version.`, state.BestQuality, state.CurrentOutput)
}

Hands-On Scenario: Copywriting Multi-Round Optimization System

Full Implementation

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "strings"
    "time"

    "github.com/google/adk-go/agent"
    "github.com/google/adk-go/team"
)

// CopywritingOptimizer copywriting optimization system
type CopywritingOptimizer struct {
    workflow *team.LoopWorkflow
}

func NewCopywritingOptimizer(model agent.Model) (*CopywritingOptimizer, error) {
    // Create the writing Agent
    writerAgent, err := agent.New(agent.Config{
        Name:        "copywriter",
        Model:       model,
        Instruction: `You are a senior copywriter. Create or optimize copy based on requirements and feedback.
Requirements:
1. Lively and infectious language.
2. Highlight core selling points.
3. Match the target audience's language habits.
4. Keep within the requested word count.`,
        Timeout: 30 * time.Second,
    })
    if err != nil {
        return nil, err
    }

    // Build evaluator
    evaluator := &CopywritingEvaluator{
        model: model,
        criteria: []EvaluationCriterion{
            {Name: "Appeal", Weight: 0.25, Prompt: "Evaluate the copy's appeal and hook strength"},
            {Name: "Clarity", Weight: 0.20, Prompt: "Evaluate the clarity of message delivery"},
            {Name: "Persuasion", Weight: 0.25, Prompt: "Evaluate persuasion and conversion potential"},
            {Name: "BrandTone", Weight: 0.15, Prompt: "Evaluate consistency with brand tone"},
            {Name: "Creativity", Weight: 0.15, Prompt: "Evaluate creativity and differentiation"},
        },
    }

    // Build composite exit condition
    exitCondition := team.NewCompositeExitCondition(team.ModeAny,
        &team.QualityThresholdCondition{Threshold: 0.85},      // quality threshold reached
        &team.MaxIterationCondition{MaxIterations: 5},          // max 5 rounds
        &team.ImprovementStallCondition{                         // improvement stalled
            WindowSize:     2,
            MinImprovement: 0.05,
        },
        &team.TimeBudgetCondition{MaxDuration: 2 * time.Minute}, // time budget
        &team.QualityRegressionCondition{Threshold: 0.10},      // quality regression
    )

    // Build workflow
    workflow := team.NewLoopWorkflow(
        team.WithAgent(writerAgent),
        team.WithEvaluator(evaluator),
        team.WithExitCondition(exitCondition),
        team.WithFeedbackBuilder(&team.DiffFeedbackBuilder{}),
        team.WithConvergenceConfig(&team.ConvergenceConfig{
            WindowSize: 3,
            Threshold:  0.01,
        }),
        team.WithTokenBudget(30000),
    )

    return &CopywritingOptimizer{workflow: workflow}, nil
}

// CopywritingEvaluator copywriting evaluator
type CopywritingEvaluator struct {
    model    agent.Model
    criteria []EvaluationCriterion
}

type EvaluationCriterion struct {
    Name   string
    Weight float64
    Prompt string
}

func (e *CopywritingEvaluator) Evaluate(ctx context.Context, output string, target string) (*team.EvaluationResult, error) {
    // Build evaluation prompt
    prompt := fmt.Sprintf(`Please evaluate the following copy, scoring each dimension (0-1, 3 decimal places).

Target: %s

Copy content:
%s

Evaluation dimensions:
`, target, output)

    for _, c := range e.criteria {
        prompt += fmt.Sprintf("- %s (weight %.0f%%): %s\n", c.Name, c.Weight*100, c.Prompt)
    }

    prompt += `
Please output in JSON format:
{
  "scores": {"dimension name": score},
  "overall": overall score,
  "feedback": "specific improvement suggestions",
  "passed": true/false
}`

    response, err := e.model.GenerateContent(ctx, prompt)
    if err != nil {
        return nil, fmt.Errorf("evaluation failed: %w", err)
    }

    var evalData struct {
        Scores   map[string]float64 `json:"scores"`
        Overall  float64            `json:"overall"`
        Feedback string           `json:"feedback"`
        Passed   bool              `json:"passed"`
    }

    if err := json.Unmarshal([]byte(response), &evalData); err != nil {
        return nil, fmt.Errorf("parse evaluation: %w", err)
    }

    criteria := make(map[string]bool)
    for name, score := range evalData.Scores {
        criteria[name] = score >= 0.7
    }

    return &team.EvaluationResult{
        Quality:  evalData.Overall,
        Feedback: evalData.Feedback,
        Passed:   evalData.Passed,
        Criteria: criteria,
        Metrics: &team.QualityMetrics{
            // Map to generic metrics
        },
    }, nil
}

func main() {
    ctx := context.Background()

    optimizer, err := NewCopywritingOptimizer(model)
    if err != nil {
        log.Fatalf("Failed to create optimizer: %v", err)
    }

    result, err := optimizer.workflow.Execute(ctx,
        `Create a social media promotional copy for a new smartwatch.
Target audience: urban professionals aged 25-35
Core selling points: 7-day battery, health monitoring, stylish design
Word count: 100-150 words`)

    if err != nil {
        log.Fatalf("Optimization failed: %v", err)
    }

    fmt.Printf("Final copy (quality: %.3f):\n%s\n\n", result.Quality, result.Output)
    fmt.Printf("Iterations: %d\n", result.Iterations)
    fmt.Printf("Exit reason: %s\n", result.ExitReason)
    fmt.Printf("Quality history: %v\n", result.QualityHistory)
    fmt.Printf("Token used: %d\n", result.TokenUsed)
    fmt.Printf("Total duration: %v\n", result.Duration)
}

Anti-Infinite-Loop Mechanisms

Multi-Layer Protection

type AntiLoopProtection struct {
    maxIterations       int           // hard limit
    timeBudget          time.Duration // time limit
    tokenBudget         int           // token limit
    similarityThreshold float64       // output similarity threshold
    history             []string      // output history
}

func (p *AntiLoopProtection) Check(state *LoopState) (bool, string) {
    // 1. Iteration count check
    if state.Iteration >= p.maxIterations {
        return true, "max_iterations"
    }

    // 2. Time check
    if time.Since(state.StartTime) >= p.timeBudget {
        return true, "time_budget"
    }

    // 3. Token check
    if state.TokenUsed >= p.tokenBudget {
        return true, "token_budget"
    }

    // 4. Similarity check (detect loops)
    if len(state.QualityHistory) >= 3 {
        current := state.CurrentOutput
        for i, hist := range p.history {
            similarity := calculateSimilarity(current, hist)
            if similarity > p.similarityThreshold {
                return true, fmt.Sprintf("repeated_output (similarity %.3f with iteration %d)",
                    similarity, i)
            }
        }
    }
    p.history = append(p.history, state.CurrentOutput)

    // 5. Quality regression check
    if len(state.QualityHistory) >= 2 {
        last := state.QualityHistory[len(state.QualityHistory)-1]
        prev := state.QualityHistory[len(state.QualityHistory)-2]
        if prev-last > 0.2 {
            return true, "significant_regression"
        }
    }

    return false, ""
}

// Text similarity calculation (simplified Jaccard)
func calculateSimilarity(a, b string) float64 {
    setA := tokenize(a)
    setB := tokenize(b)

    intersection := 0
    for token := range setA {
        if setB[token] {
            intersection++
        }
    }

    union := len(setA) + len(setB) - intersection
    if union == 0 {
        return 1.0
    }

    return float64(intersection) / float64(union)
}

func tokenize(text string) map[string]bool {
    tokens := make(map[string]bool)
    words := strings.Fields(text)
    for _, word := range words {
        tokens[strings.ToLower(word)] = true
    }
    return tokens
}

State Evolution and History Management

Compressed Storage

type CompressedLoopHistory struct {
    iterations     int
    qualityTrend   []float64
    bestOutputs    []OutputSnapshot
    compressionCfg *CompressionConfig
}

type OutputSnapshot struct {
    Iteration int
    Quality   float64
    Hash      string // content hash for deduplication
    Summary   string // summary replaces full content
}

func (h *CompressedLoopHistory) Add(output string, quality float64, iteration int) {
    // Only keep results from critical iterations
    if h.shouldKeep(iteration, quality) {
        hash := sha256.Sum256([]byte(output))

        h.bestOutputs = append(h.bestOutputs, OutputSnapshot{
            Iteration: iteration,
            Quality:   quality,
            Hash:      fmt.Sprintf("%x", hash[:8]),
            Summary:   summarize(output, 200),
        })
    }

    h.qualityTrend = append(h.qualityTrend, quality)
    h.iterations = iteration
}

func (h *CompressedLoopHistory) shouldKeep(iteration int, quality float64) bool {
    // Keep: first, best, and most recent
    if iteration == 1 {
        return true
    }

    if quality > h.getBestQuality() {
        return true
    }

    if iteration > h.iterations-2 {
        return true
    }

    return false
}

Common Questions in Depth

Q: How does a Loop workflow ensure convergence?

A: Convergence cannot be absolutely guaranteed, but the probability can be increased by:

  1. Quality monotonicity: ensure evaluator feedback actually points toward improvement.
  2. Feedback quality: use detailed, actionable feedback rather than vague “not good enough” comments.
  3. Iterative cooling: reduce the amplitude of changes in later iterations to avoid oscillation.
  4. Multi-start: run multiple Loops from different initial outputs and select the best result.

Q: What if the evaluator itself is inaccurate?

A: Three layers of protection:

  1. Multi-evaluator voting: use 3 different evaluators and take the median.
  2. Human-in-the-loop: introduce human review at critical nodes.
  3. Evaluator calibration: regularly calibrate evaluation criteria with labeled data.

Q: How is Loop token consumption controlled?

A: A combination of strategies:

  • Set a hard budget cap.
  • Use a lightweight model for evaluation.
  • Compress historical context (keep only summaries).
  • Early termination (exit early when quality improvement < 0.01).

Next Steps

You now have a deep understanding of Loop workflow iterative optimization and convergence control. Next, explore the Custom Workflow—flexible orchestration, dynamic scheduling, and complex scenario adaptation for custom workflows.

Parallel Workflow | Custom Workflow →


Want to learn more Go ADK hands-on? Follow the “Mengshou Programming” channel for weekly updates on Go and AI programming.

Frequently Asked Questions

What tasks are suitable for the Loop workflow?

Tasks that require multiple rounds of iterative optimization and cannot be completed in one pass, such as copy refinement and code review fixes.

How do I ensure Loop convergence?

Ensure quality monotonicity, provide concrete actionable feedback, use iterative cooling to reduce change amplitude in later stages, and use multi-start to select the best result.

What if the evaluator itself is inaccurate?

Use multi-evaluator voting, introduce human review at critical nodes, and regularly calibrate evaluation criteria with labeled data.

How can Loop token consumption be controlled?

Set a hard budget cap, use a lightweight model for evaluation, compress historical context, and terminate early when quality improvement falls below a threshold.

What are the termination conditions for Loop?

Common termination conditions include quality score reaching the target, reaching the maximum number of iterations, token budget exhaustion, or improvement falling below a threshold.