0Pricing
AI Engineering Academy · Lesson

The Code Execution Loop

Design the write-execute-observe loop where the agent generates code, a sandboxed executor runs it, stdout and stderr are captured and fed back as observations, and the agent fixes errors.

The Code Execution Loop is a free AI Engineering Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Engineering Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Code Agents: Writing and Running Code

A code execution agent is a special type of AI agent that solves problems by writing code, running it, observing the output, and iterating until the task is complete. Unlike agents that only use pre-defined tools, code agents create new computational tools on the fly. This makes them extraordinarily flexible: any task that can be programmed can be attempted by a code agent.

The Write-Execute-Observe Loop

The core of a code execution agent is a loop with three steps: Write — the LLM generates Python code to solve the current step of the task. Execute — the code is run in a sandboxed environment and stdout/stderr are captured. Observe — the execution output is fed back to the LLM as a new observation, which it uses to decide what to write next. This loop continues until the task is complete or an iteration limit is reached.

def code_execution_loop(task: str, max_iterations=10):
    messages = [
        {'role': 'system', 'content': 'You are a Python coding agent. Write code to solve tasks step by step.'},
        {'role': 'user', 'content': task}
    ]
    
    for i in range(max_iterations):
        # WRITE: LLM generates code
        response = llm.complete(messages)
        code = extract_code_block(response)
        
        if not code:
            return response  # LLM gave a final answer without code
        
        # EXECUTE: run the code
        output, error = execute_safely(code)
        
        # OBSERVE: feed output back
        observation = f'Output:\n{output}' if not error else f'Error:\n{error}'
        messages.append({'role': 'assistant', 'content': response})
        messages.append({'role': 'user', 'content': observation})
    
    return 'Max iterations reached'

Extracting Code Blocks from LLM Output

LLMs typically wrap generated code in markdown fenced code blocks: ```python ... ```. Your execution loop must reliably extract the code from these blocks before running it. Use a regex or a simple parser to find fenced blocks and handle edge cases like nested backticks, multiple code blocks in one response, or code without a language specifier.

import re

def extract_code_block(response: str) -> str | None:
    # Match ```python ... ``` or ``` ... ```
    pattern = r'```(?:python)?\n(.*?)```'
    matches = re.findall(pattern, response, re.DOTALL)
    if not matches:
        return None
    # Return the LAST code block (often the most complete version)
    return matches[-1].strip()

# Test
sample_response = '''I will calculate the sum:\n\n```python\nnumbers = [1, 2, 3, 4, 5]\nprint(sum(numbers))\n```'''
code = extract_code_block(sample_response)
print(repr(code))  # 'numbers = [1, 2, 3, 4, 5]\nprint(sum(numbers))'

Capturing stdout and stderr

When executing agent-generated code, you must capture both stdout (normal output) and stderr (error messages and tracebacks) to feed back to the LLM. Using Python's subprocess module is the simplest approach: run the code as a separate process and capture both streams. Set a timeout to prevent code that hangs from blocking the agent.

import subprocess
import tempfile
import os

def execute_code(code: str, timeout_seconds=30) -> tuple[str, str]:
    # Write code to a temp file
    with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
        f.write(code)
        tmp_path = f.name
    
    try:
        result = subprocess.run(
            ['python', tmp_path],
            capture_output=True,
            text=True,
            timeout=timeout_seconds
        )
        stdout = result.stdout[:5000]  # cap output length
        stderr = result.stderr[:2000]  # cap error length
        return stdout, stderr
    except subprocess.TimeoutExpired:
        return '', f'TimeoutError: code exceeded {timeout_seconds}s limit'
    finally:
        os.unlink(tmp_path)  # always clean up temp file

Handling Errors Intelligently

When code produces an error, the error message (traceback, exception type, line number) is fed back to the LLM as an observation. The LLM then analyzes the error and writes corrected code. To help the LLM debug effectively, include the exact error text in the observation, include the line of code that caused the error, and ask the LLM explicitly to fix it before trying again.

def format_error_observation(code: str, error: str) -> str:
    lines = code.split('\n')
    
    # Extract line number from traceback if available
    line_match = re.search(r'line (\d+)', error)
    error_context = ''
    if line_match:
        line_num = int(line_match.group(1))
        if 1 <= line_num <= len(lines):
            error_context = f'\nFailing line: {lines[line_num - 1].strip()}'
    
    return f'''Execution failed with error:{error_context}\n\n{error}\n\nPlease analyze the error and write corrected code.'''

# Usage in loop
output, err = execute_code(code)
if err:
    observation = format_error_observation(code, err)
else:
    observation = f'Output:\n{output}\n\nContinue with the next step or provide the final answer.'

Detecting Task Completion

The code execution loop must know when to stop. Common completion signals include: the LLM returns a response with no code block (only a natural language answer), the LLM writes a special marker like # TASK_COMPLETE, the code produces an output file that the agent was asked to create, or a validation function confirms the output meets the success criteria. Always implement an iteration limit as a fallback.

COMPLETION_MARKERS = ['TASK COMPLETE', 'FINAL ANSWER:', 'DONE:']

def is_task_complete(response: str, code: str | None, output: str, success_fn=None) -> bool:
    # Check for explicit completion markers
    for marker in COMPLETION_MARKERS:
        if marker in response.upper():
            return True
    
    # No code generated - LLM is done
    if code is None:
        return True
    
    # Custom success function (e.g., check output file exists)
    if success_fn and success_fn(output):
        return True
    
    return False

# Example success function
def report_was_generated(output: str) -> bool:
    import os
    return os.path.exists('analysis_report.pdf')

Passing Data Between Iterations

One challenge in code execution loops is state persistence between iterations. Each code block runs in a fresh Python process, so variables defined in one iteration are not available in the next. Solutions include: writing intermediate data to files, using a persistent process with code injected via exec(), or explicitly instructing the LLM to redefine necessary variables at the start of each code block.

# Strategy 1: Save to files between iterations
# Iteration 1: LLM writes
'''
import pandas as pd
df = pd.read_csv('data.csv')
df_cleaned = df.dropna()
df_cleaned.to_parquet('cleaned.parquet')  # save for next iteration
print('Cleaned rows:', len(df_cleaned))
'''

# Iteration 2: LLM reads previous output
'''
import pandas as pd
df = pd.read_parquet('cleaned.parquet')  # reload from file
result = df.groupby('category').sum()
result.to_csv('result.csv')
print(result.head())
'''

Structuring the System Prompt

The system prompt for a code execution agent must teach the LLM how to use the loop effectively. Key elements to include: how to write executable Python code blocks, how to check intermediate outputs before proceeding, how to signal task completion, what libraries are available in the sandbox, and what file paths the agent is allowed to read from and write to.

CODE_AGENT_SYSTEM_PROMPT = '''
You are a Python code execution agent. Solve tasks by writing and running code.

Rules:
1. Write ALL code inside ```python ... ``` blocks.
2. Write small, testable code blocks - do not try to do everything in one block.
3. When you see execution output, analyze it and decide whether to continue or fix errors.
4. Available libraries: pandas, numpy, matplotlib, requests, json, os, pathlib.
5. You can read/write files only in /workspace/ directory.
6. When the task is fully complete, say TASK COMPLETE and summarize the result.
7. If you are stuck after 3 attempts at the same error, say ESCALATE and explain the problem.
'''

Output Truncation and Token Management

Agent-generated code can produce enormous outputs: DataFrame printouts, long lists, verbose logs. Feeding the full output back to the LLM wastes tokens and can overflow the context window. Always truncate execution output before adding it to the conversation. A good default is 2000-5000 characters, plus a note that the output was truncated so the LLM knows it may have more to explore.

MAX_OUTPUT_CHARS = 3000

def format_observation(stdout: str, stderr: str) -> str:
    if stderr:
        # Errors take priority - show full error
        return f'Error:\n{stderr[:MAX_OUTPUT_CHARS]}'
    
    if len(stdout) > MAX_OUTPUT_CHARS:
        truncated = stdout[:MAX_OUTPUT_CHARS]
        remaining = len(stdout) - MAX_OUTPUT_CHARS
        return f'{truncated}\n... [output truncated, {remaining} more characters]'
    
    return f'Output:\n{stdout}' if stdout else 'Code executed successfully (no output)'

The Code Loop in Practice

A real code execution agent for data analysis might run 5-15 iterations to: load a CSV, explore its structure, clean the data, compute statistics, generate a chart, and produce a summary report. Each iteration builds on the previous, with the LLM adapting its approach based on actual data values and shapes discovered at runtime. This dynamic adaptation is what makes code agents so powerful for exploratory tasks.

Security Considerations in Code Loops

Running LLM-generated code on your infrastructure is a significant security risk. Never run agent code without sandboxing. The LLM might inadvertently (or due to prompt injection) generate code that: deletes files, exfiltrates data, makes network requests to external services, or exhausts system resources. Always execute in an isolated environment with strict resource limits, network restrictions, and filesystem boundaries.

Quick Check

Test your understanding of the code execution loop from this lesson.

Lesson Recap

In this lesson you learned: the write-execute-observe loop is the core pattern of code execution agents, stdout and stderr from execution are fed back as observations that guide the LLM's next code generation, and output truncation and iteration limits are essential safety mechanisms. Next up we explore sandboxing code execution with Docker and RestrictedPython.

Frequently asked questions

Is the “The Code Execution Loop” lesson free?

Yes — the full text of “The Code Execution Loop” is free to read here on the web, and the AI Engineering Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Engineering Academy course, upgrade to CoddyKit PRO.

What will I learn in “The Code Execution Loop”?

Design the write-execute-observe loop where the agent generates code, a sandboxed executor runs it, stdout and stderr are captured and fed back as observations, and the agent fixes errors. You practise AI Engineering Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start AI Engineering Academy?

No prior experience is required. AI Engineering Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “The Code Execution Loop” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this AI Engineering Academy lesson?

Yes. Every AI Engineering Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The Code Execution Loop
  2. Sandboxing with Docker and RestrictedPython
  3. State Management Across Execution Steps
  4. Building a Data Analysis Agent
← Back to AI Engineering Academy