DEV Community

Solon Framework
Solon Framework

Posted on

Self-Healing Software Development: Automating Code Generation, Testing, and Fixing with Solon AI Loop Engine

Software agents are transitioning from stateless assistants to autonomous, loop-driven operators. While generating boilerplate code is easy, the real challenge in software engineering is maintaining correctness: resolving compilation errors, fixing failing unit tests, and adhering to strict quality gates.

In traditional architectures, developers act as the loop execution engine: reviewing errors, editing code, and rebuilding until the tests pass.

With Solon AI (v4.0.5), the newly introduced solon-ai-loop plugin codifies this cyclic corrective behavior directly into your backend architecture. By leveraging the Loop Engine, custom Loop Strategies, and automated Quality Gates, we can construct self-healing software development agents that iterate autonomously on source code until all gates are met.

In this article, we will explore the core design of the Solon AI Loop Engine and build a complete, production-grade Self-Healing Coding & Verification Pipeline using Java 8+ and GraalVM-compatible components.


Understanding the Solon AI Loop Engine

The solon-ai-loop package provides a structured framework for stateful, iterative execution of tasks. Rather than letting an LLM run indefinitely or writing custom spaghetti loops, Solon AI models loops around three main components:

  1. LoopEngine: The core runtime that starts, pauses, resumes, and monitors loop sessions.
  2. LoopStrategy: Encapsulates the state machine defining how iterations progress, when to check constraints, and what dictates failure or success.
  3. Validator & QualityGate: Decoupled verification components that assert whether an output meets quality thresholds (e.g., compilation, style rules, or test suites).

Three strategies are pre-built to match common software workflows:

  • RalphLoopStrategy: A PRD-driven, story-by-story implementation loop that tracks progress, learnings, and file modifications.
  • TeamPipelineStrategy: A multi-phase transition pipeline (PLAN $\rightarrow$ PRD $\rightarrow$ EXEC $\rightarrow$ VERIFY $\rightarrow$ FIX).
  • UltraQAStrategy: A testing-focused gatekeeper that runs code checks, tracks failures, and prevents runaway token usage using Same-Failure Detection.

Designing a Self-Healing Development Pipeline

Let's design a pipeline where an autonomous agent compiles, runs test suites, and refactors its own code when bugs occur.

+-------------------------------------------------------------+
|                       Solon AI Loop                         |
|                                                             |
|   +----------+        +------------+        +-----------+   |
|   |  Agent   |------->| Build Gate |------->| Test Gate |   |
|   |  (EXEC)  |        | (VERIFY)   |        | (VERIFY)  |   |
|   +----------+        +------------+        +-----------+   |
|        ^                     |                    |         |
|        |                     v (Fail)             v (Fail)  |
|        |              +------------+        +-----------+   |
|        +--------------|  Analyze   |<-------| Normalize |   |
|          (Fix Loop)   |  & Fix     |        | Failure   |   |
|                       +------------+        +-----------+   |
+-------------------------------------------------------------+
Enter fullscreen mode Exit fullscreen mode

To prevent the agent from getting stuck in an infinite loop trying the exact same fix on a hard-to-resolve bug, our pipeline uses the UltraQAStrategy's built-in Same-Failure Detection. If the normalized error output remains identical across 3 consecutive attempts, the engine aborts the run with a SAME_FAILURE status, saving token usage and requesting human intervention.


Step-by-Step Implementation

1. Adding Dependencies

Include the core Solon AI dependencies alongside the new loop engine plugin in your pom.xml:

<dependencies>
    <!-- Solon AI Core -->
    <dependency>
        <groupId>org.noear</groupId>
        <artifactId>solon-ai-core</artifactId>
        <version>4.0.5</version>
    </dependency>

    <!-- Solon AI Loop Engine Plugin -->
    <dependency>
        <groupId>org.noear</groupId>
        <artifactId>solon-ai-loop</artifactId>
        <version>4.0.5</version>
    </dependency>

    <!-- Chat Model Dialect for model integration -->
    <dependency>
        <groupId>org.noear</groupId>
        <artifactId>solon-ai-dialect-openai</artifactId>
        <version>4.0.5</version>
    </dependency>
</dependencies>
Enter fullscreen mode Exit fullscreen mode

2. Defining the Self-Healing Code Validator

First, we define a Validator that represents our physical quality gate checks. In a real-world scenario, this validator runs local shell commands (such as mvn compile or mvn test) or parses project compilation structures.

Here is the implementation of a mock compiler and test validator that simulates a compilation success on the 3rd attempt:

import org.noear.solon.ai.loop.validator.*;

public class SelfHealingCodeValidator implements Validator {
    private int compileAttempts = 0;

    @Override
    public ValidationResult validate(Object result, ValidationCriteria criteria) {
        return validateIteration(result, null);
    }

    @Override
    public ValidationResult validateQualityGate(QualityGate gate, Object result) {
        // Evaluate based on the gate type ("build" or "test")
        if ("build".equals(gate.getName())) {
            compileAttempts++;
            if (compileAttempts < 3) {
                return ValidationResult.failed(
                    "Compilation Error", 
                    "error: cannot find symbol\n  symbol: class OrderProcessor\n  location: package com.demo"
                );
            }
            return ValidationResult.passed("Build success!");
        } else if ("test".equals(gate.getName())) {
            return ValidationResult.passed("All unit tests passed!");
        }
        return ValidationResult.passed("Quality check skipped.");
    }

    @Override
    public ValidationResult validateIteration(Object iterationResult, ValidationContext context) {
        if (iterationResult == null) {
            return ValidationResult.failed("Execution Result Null", "No artifacts were produced.");
        }
        return ValidationResult.passed("Iteration valid.");
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Configuring the Loop Session with UltraQA Strategy

Now, we set up the LoopEngine and use UltraQAStrategy to drive our self-healing loop. We'll target the TESTS goal type and configure a maximum of 5 overall iterations:

import org.noear.solon.ai.loop.config.LoopConfig;
import org.noear.solon.ai.loop.config.LoopEngineConfig;
import org.noear.solon.ai.loop.engine.*;
import org.noear.solon.ai.loop.strategy.UltraQAStrategy;
import org.noear.solon.ai.loop.strategy.UltraQAStrategy.UltraQAGoalType;
import org.noear.solon.ai.loop.strategy.UltraQAStrategy.UltraQAExitReason;
import org.noear.solon.ai.loop.validator.QualityGate;

import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class SelfHealingPipelineDemo {

    public static void main(String[] args) throws InterruptedException {
        // 1. Initialize the Loop Engine
        LoopEngineConfig engineConfig = LoopEngineConfig.builder()
                .monitoringEnabled(true)
                .debuggingEnabled(true)
                .build();
        LoopEngine engine = new SimpleLoopEngine(engineConfig);

        // 2. Configure the Quality Gates (Build Gate followed by Test Gate)
        QualityGate buildGate = QualityGate.build(); // Runs compilation & dependency validation
        QualityGate testGate = QualityGate.test();   // Runs unit & integration tests

        // 3. Configure the QA Loop Strategy
        UltraQAStrategy qaStrategy = UltraQAStrategy.builder()
                .gates(Arrays.asList(buildGate, testGate))
                .goalType(UltraQAGoalType.TESTS)
                .parallelTesting(false)
                .maxTestAttempts(5) // Max cycles
                .strictMode(true)
                .build();

        // 4. Bind configuration
        Map<String, Object> params = new HashMap<>();
        params.put("workDir", "./demo-project");

        LoopConfig loopConfig = LoopConfig.builder()
                .taskDescription("Fix missing OrderProcessor class implementation in com.demo")
                .strategy(qaStrategy)
                .validator(new SelfHealingCodeValidator())
                .maxIterations(10)
                .verificationRequired(true)
                .statePersistenceEnabled(true)
                .parameters(params)
                .build();

        // 5. Start the Loop Session
        System.out.println("Starting Self-Healing pipeline...");
        LoopSession session = engine.start(loopConfig);

        // 6. Listen to live iteration state updates
        session.onIterationComplete(iterResult -> {
            System.out.printf("[Iteration %d] Status: %s | Message: %s | Duration: %d ms\n",
                    iterResult.getNumber(),
                    iterResult.getState(),
                    iterResult.getMessage(),
                    iterResult.getDuration().toMillis()
            );

            // Check metadata parameters
            Map<String, Object> meta = iterResult.getMetadata();
            if (meta != null && meta.containsKey("failures")) {
                System.out.printf("  -> Cumulative Failures Recorded: %s\n", meta.get("failures"));
            }
        });

        session.onStateChange(state -> {
            System.out.printf("[State Event] Loop transitioned to: %s\n", state);
        });

        // 7. Await termination (max 10 seconds)
        session.waitForCompletion(Duration.ofSeconds(10));

        // 8. Print out final analysis
        LoopResult result = session.getResult();
        if (result != null) {
            System.out.println("\n====================================");
            System.out.println("Pipeline Final Summary:");
            System.out.printf("  Session ID: %s\n", result.getSessionId());
            System.out.printf("  Successful: %s\n", result.isSuccess());
            System.out.printf("  Final State: %s\n", result.getFinalState());
            System.out.printf("  Total Iterations Run: %d\n", result.getTotalIterations());
            System.out.printf("  Total Execution Time: %d ms\n", result.getTotalDuration().toMillis());

            UltraQAExitReason exitReason = qaStrategy.getFinalExitReason(session.getContext());
            System.out.printf("  Execution Termination Reason: %s (%s)\n", 
                    exitReason.name(), 
                    exitReason.getDescription()
            );
            System.out.println("====================================");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Exploring the Run Traces

When running the pipeline, the console outputs how the state machine dynamically handles failures, fixes, and gates:

Starting Self-Healing pipeline...
[State Event] Loop transitioned to: EXECUTING
[Iteration 1] Status: FIXING | Message: Gate failed: build | Duration: 4 ms
  -> Cumulative Failures Recorded: 1
[State Event] Loop transitioned to: FIXING
[Iteration 2] Status: FIXING | Message: Gate failed: build | Duration: 2 ms
  -> Cumulative Failures Recorded: 2
[State Event] Loop transitioned to: FIXING
[Iteration 3] Status: VERIFYING | Message: Gate failed: test | Duration: 1 ms
  -> Cumulative Failures Recorded: 2
[Iteration 4] Status: COMPLETED | Message: All quality gates passed | Duration: 2 ms
  -> Cumulative Failures Recorded: 2
[State Event] Loop transitioned to: COMPLETED

====================================
Pipeline Final Summary:
  Session ID: 4c3ab871-6c2e-4b20-80de-cd13ad91cf76
  Successful: true
  Final State: COMPLETED
  Total Iterations Run: 4
  Total Execution Time: 9 ms
  Execution Termination Reason: GOAL_MET (目标达成)
====================================
Enter fullscreen mode Exit fullscreen mode

How it resolved:

  1. Iteration 1 & 2: The compile validator returned Compilation Error. Because the gate failed, the loop state changed to FIXING, indicating to the agent that corrective steps were needed.
  2. Iteration 3: On the 3rd attempt, the compile check passed. The runner moved on to check the test gate, which failed because the test suite had not yet run under the verified state. The loop transitioned to VERIFYING.
  3. Iteration 4: Both gates were fully verified, culminating in All quality gates passed and terminating with GOAL_MET.

Under the Hood: Same-Failure Guard & Message Normalization

If we simulate a recurring bug where the agent repeats the exact same error, the pipeline guards against runaway execution.

The UltraQAStrategy class implements normalization rules to sanitize error logs before registering them. Dynamic parameters like timestamps, random file lines, execution times, and version numbers are removed:

private String normalizeFailure(String failure) {
    if (failure == null) return "";
    return failure
            .replaceAll("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}", "")  // Remove ISO timestamps
            .replaceAll(":\\d+:\\d+", "")                        // Remove line:column numbers
            .replaceAll("\\d+ms", "")                              // Remove durations
            .replaceAll("line \\d+", "line N")                  // Generalize line numbers
            .replaceAll("\\s+", " ")                            // Collapse white spaces
            .trim()
            .toLowerCase();
}
Enter fullscreen mode Exit fullscreen mode

If failures.get(i).equals(lastFailure) matches the same pattern 3 times consecutively, the engine breaks:

[Iteration 1] Status: FIXING | Message: Gate failed: build | Error: compilation error at line 42
[Iteration 2] Status: FIXING | Message: Gate failed: build | Error: compilation error at line 42
[Iteration 3] Status: FIXING | Message: Gate failed: build | Error: compilation error at line 42

Execution Termination Reason: SAME_FAILURE (相同的失败重复出现)
Enter fullscreen mode Exit fullscreen mode

By adding this check, you keep your LLM agent from entering a repeating loop when encountering system errors or environmental problems, preventing unnecessary API costs.


Conclusion

The Loop Engine in Solon AI (v4.0.5) shifts agent design from stateless, unidirectional workflows to robust, self-verifying systems. By pairing structural LLM generation with local compiling/testing tools via UltraQAStrategy or TeamPipelineStrategy, you can construct enterprise-grade pipelines capable of autonomous bug resolution.

Integrate the solon-ai-loop plugin into your current codebase and begin building self-healing, agentic workflows today!

For more documentation and code examples, visit the official repository:

Top comments (0)