The most important thing to consider is what problem you're trying to solve. The second is how you represent knowledge. The third is how you choose to find improvements. Let's take these steps one at a time:
What you're trying to solve
Running these simulations usually requires a purpose. This purpose something to judge against: an evaluation function that says how close you are to your goal. Without a goal, a simulation can manipulate itself... but it would just put out random material.
How you represent knowledge
If you're trying to reach a goal, then the code needs two parts: the goal-oriented material, which doesn't change, and the learning material, which does change. Some use a language like LISP, where data and code are of the same format. I created a simple virtual machine with the functions that I needed, putting knowledge in an assembly language. There are myriads of correct answers, but you need to choose.
How you find improvements
The most common way to examine and upgrade code is through a genetic algorithm strategy. But other hill-climbing algorithms and optimization algorithms work well too. I've used genetic algorithms, simulated annealing, and neural networks in metaprogramming experiments.