Self-Programming AI: Code-Learning Agents for Autonomous Refactoring and Architectural Evolution — part 3/7
Cleaned-text code points [26943, 40889). Bibliography omitted here; source.md includes it.
The Task Planning Module serves as the entry point of the pipeline. It takes a natural-language specification of the desired functionality and uses GPT-3.5 (via the OpenAI API) to produce an initial solution comprising both Python source code and an associated test suite. To ensure well-structured output, the TPM leverages GPT-3.5’s function- calling ability, which allows the model to return data in a specified schema (in this case, separate fields for code and tests). Concretely, the module provides the LLM with a description of the task and a function signature (or JSON schema) for the expected output, so that GPT returns, for example, an object with a "solution_code" string and a "test_code" string. By guiding the LLM with function-calling, the TPM generates an i nitial code implementation along with unit tests in a single step, neatly partitioned. This structured approach yields an executable codebase and a baseline test suite without manual intervention. The TPM effectively “plans” the project by producing not only the solution attempt but also the criteria by which it will be judged (the tests), giving the agent a clear starting point for the self-improvement loop. Execution and Evaluation Module Once an initial code version and test suite are available, the Execution and Evaluation Module takes over to automatically run the code and assess its performance. This module invokes Pytest to execute the generated test suite against the current code. The outcomes (which tests passed or failed, and any error traces) are recorded to identify functional correctness issues. In addition, this module measures code coverage using Coverage.py, quantifying the proportion of the code exercised by the tests. A high coverage (e.g. ≥80%) indicates that the tests thoroughly exercise the code, which is a target criterion for success. Next, the module evaluates code complexity and maintainability using Radon, a static analysis tool. Radon computes the cyclomatic complexity of the code (McCabe’s complexity) and the maintainability index, among other metrics. The cyclomatic complexity scores help ensure that functions are not overly complex (the goal is to keep complexity ≤10 for any given function, to maintain clarity and simplicity in logic). A maintainability index is also obtained as an aggregate indicator of code readability and technical debt. Furthermore, the module runs Pylint on the code to perform linting—checking for syntax errors, anti-patterns, and style guideline violations. Pylint produces a report including a numeric score and any warnings or errors; the ultimate aim is to have 0 lint errors, meaning the code adheres to standard style and has no static analysis flags. All these results — test pass/fail counts, coverage percentage, complexity metrics, maintainability index, and lint findings — are aggregated into a structured JSON-formatted report. This report provides the quantitative feedback necessary for the agent to judge the current solution’s quality. In summary, the Execution and Evaluation Module serves as the “sensing” mechanism of the closed loop, automatically gathering feedback on both functional correctness and code quality after each iteration. Improvement Module The Improvement Module is the core of the system’s self-refinement capability. It takes the JSON report from the evaluation phase as input and prompts GPT-3.5 to analyze the feedback and propose targeted improvements to the code (and tests). This module essentially asks the LLM to act as a code reviewer and debugger: the prompt includes a summary of which tests failed (or which requirements are not met), the coverage shortfall if any, complexity or style problems identified, etc., and instructs GPT-3.5 to suggest changes that would resolve these issues. Importantly, the Improvement Module guides GPT to output its suggestions in the form of AST-level patches or diff-like instructions, rather than monolithic re-written code. By focusing on AST-level edits, the model suggests structural code modifications — for example, “simplify the logic in function X to reduce cyclomatic complexity,” or “add a check to handle the edge-case that caused test Y to fail,” accompanied by an outline of the code change (such as which lines to insert or modify). The model can also recommend new test cases if the coverage is below target or if certain functionality lacks tests. All suggestions are produced in a machine-readable format (e.g. a list of edit operations or a unified diff) so that they can be automatically applied by the next module. In effect, the Improvement Module serves as an autonomous code editor guided by analytical feedback: it closes the loop by using the evaluation metrics to drive code evolution, mimicking a develop er who iteratively fixes bugs and refactors code to meet quality standards. This design is informed by the principle that LLMs can iteratively debug and refine their outputs when given structured feedback, enabling the agent to progressively converge on a correct and clean solution. Patch Application Engine Applying the code modifications recommended by the LLM is handled by the Patch Application Engine. This engine interprets the diff/patch instructions from the Improvement Module and programmatically edits the codebase. We implement this using LibCST (Library for Concrete Syntax Tree), a parser and transformer library for Python. LibCST parses Python source code into a concrete syntax tree that preserves all formatting details (comments, whitespace, etc.), and it is designed for building automated code refacto ring tools (so-called “codemods”). By using LibCST, the patch engine can insert, remove, or alter AST nodes in the code in a reliable way, ensuring that the result is syntactically correct Python code and that existing formatting/style is retained. For example, if the improvement suggestions say to “add a parameter to function foo” or “change the return logic of bar() to fix an off-by-one error,” the Patch Application Engine will locate the relevant AST nodes (function definitions, return statements, etc.) and perform the modifications. This approach of applying structural edits (as opposed to naive string substitutions) greatly reduces the risk of introducing syntax errors or breaking code in the process of refactoring. After applying all suggested edits, the engine produces an updated version of the code (and updates the test suite if new tests were suggested). The updated code is then fed back into the Execution and Evaluation Module for re-testing in the next iteration. The Patch Application Engine thus acts as the system’s “effectuator,” reliably turning the AI’s improvement suggestions into actual code changes, enabling automated refactoring and evolution of the codebase with each loop. Retry and Rate Limit Wrapper In deploying this autonomous pipeline, one practical challenge is ensuring reliable communication with the OpenAI GPT-3.5 API, especially under strict rate limits or potential network instabilities. To address this, all LLM calls (in the TPM and Improvement modules) are encapsulated within a Retry and Rate Limit Wrapper. This wrapper intercepts any API responses indicating a rate limit error (HTTP 429 “Too Many Requests” or related quota issues) and automatically retries the call with exponential backoff. Specifically, if GPT-3.5 returns a rate-limit error, the wrapper will pause for a short delay and then resend the request; if the error persists, the delay interval increases exponentially on subsequent retries. This continues until the request succeeds or a max imum number of retries is reached. By using exponential backoff as recommended in OpenAI’s best practices, the system avoids flooding the API and gracefully handles temporary throttling. The wrapper also handles other transient errors (like network timeouts), ensuring that a single failed attempt does not derail the entire coding loop. In essence, the Retry and Rate Limit Wrapper adds robustness to the agent architecture, allowing the self-programming loop to run unattended for extended periods and multiple iterations without human intervention, even in the face of external API limitations. This component is crucial for maintaining the autonomy of the system, as it abstracts away API reliability concerns and lets the core logic focus on code generation and improvement. Logging and Version Tracking Throughout the iterative refinement process, the system maintains thorough logging and version tracking using MLflow. MLflow is an open-source platform for experiment tracking, and here it is used to record the state and metrics of each iteration in the self-programming loop. After every iteration (one cycle of code generation → evaluation → improvement → patching), the system logs key metrics to an MLflow run – including the number of tests passed/failed, the coverage percentage, complexity scores, maintainability index, lint score, and any other relevant quality indicators. It also logs parameters such as the iteration count and any configuration settings, and can save artifacts like the actual code files or diff patches from that iteration. By logging every refinement step, we create a detailed trajectory of the code’s evolution. This provides an audit trail of how the solution improved over time and helps in analyzing the effectiveness of each change. For example, one can examine the MLflow logs to see how coverage improved from 50% to 85% over successive iterations, or how the lint error count dropped to zero. Moreover, MLflow’s version tracking allows easy comparison between different runs or different agent configurations. If the agent is run multiple times on the same problem (or on variants of a problem), MLflow records enable researchers to compare convergence speed and final quality across runs. This Logging and Version Tracking mechanism is therefore an integral part of the framework’s experimental rigor: it treats each self-programming session as an experiment and ensures that the progress and outcomes are reproducible and analyzable. In an academic context, such tracking is invaluable for evaluating the efficacy of the self -programming approach and guiding further improvements to the system’s design. Iterative Refinement Loop All the above modules are interconnected in an iterative loop that drives the agent toward an optimal solution. The loop begins with the Task Planning Module generating an initial code and test suite from the specification. Then each iteration proceeds as follows: the Execution and Evaluation Module runs tests and computes metrics on the latest code version, producing a feedback report; the Improvement Module reads this feedback and proposes code patches and possibly new tests to address any shortcomings; the Patch Application Engine applies these patches to yield an improved codebase. The cycle then repeats with the updated code being re- evaluated. This closed-loop process continues, refining the program in each iteration, much like an autonomous developer repeatedly running tests and fixing issues. The loop will terminate when either the solution meets all the predefined success criteria or a maximum number of iterations is reached. The success criteria are set to ensure both functional correctness and code quality, and include: • All tests passing: the entire test suite must succeed (no failing test cases), indicating the code meets the specification’s requirements. • Coverage ≥ 80%: at least 80% of the code’s lines are executed by the tests, ensuring that the test suite is thorough and covers the majority of the codebase. • Cyclomatic complexity ≤ 10: no function or module in the code has a complexity score above 10, enforcing that the solution is refactored into reasonably simple components (complexity beyond 10 is often considered a warning sign for maintainability). • Zero lint errors: static analysis (Pylint) reports no errors or serious warnings, confirming that the code adheres to standard coding practices and style guides (e.g. PEP-8 compliance, no u nused variables or obvious code smells). If all these conditions are satisfied, the agent deems the code "successful" and the process stops, having achieved a correct and maintainable solution. These criteria ensure that the final output is not only functionally correct but also of production-level quality – a distinguishing goal of self-programming AI, which seeks to produce code that is clean and evolvable, not just code that works. In cases where the criteria are not met but the maximum iteration count is reached (to prevent infinite loops in pathological scenarios), the process will halt and log the best solution obtained so far, along with the metrics indicating where it fell short. In summary, this system architecture realizes an autonomous code-writing agent that learns from its own mistakes in a closed feedback loop. By combining planning (initial code generation), execution (testing and analysis), and iterative refinement (automated patching of code), the agent progressively improves both the functional correctness and structural quality of the software it produces. This framework exemplifies code-learning behavior: the AI agent effectively “programs itself” by repeatedly adjusting its output in response to quantitative feedback, which is a step toward software that can self-refactor and evolve its architecture over time. Such a self-programming loop highlights the potential of LLM-based agents in software engineering – they can au tonomously perform test-driven development and refactoring, achieving high success rates and reliability through iterative self-correction. The result is a system capable of delivering code that not only passes all tests but is also optimized for readability,