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,