In the previous article, we learned what AI coding agents are. Now let’s build one.

Not a toy demo. A real agent that reads your code, finds problems, fixes them, runs tests, and commits the result. You will understand how agents work from the inside.

We will use Python and the Anthropic SDK (Claude API). The concepts apply to any AI provider.

What We Are Building

A simple agent that:

  1. Reads a Python file
  2. Sends it to Claude with instructions to fix linting errors
  3. Writes the fixed code back to the file
  4. Runs the linter to verify the fix
  5. If it still fails — tries again (up to 3 attempts)
  6. Commits the result with git

This is the same loop that Claude Code and Copilot Agent use internally — just smaller and simpler.

Prerequisites

You need:

pip install anthropic

Set your API key:

export ANTHROPIC_API_KEY="sk-ant-..."

Step 1: Read a File

Every agent starts by reading the code it needs to work on:

# agent.py

import sys

def read_file(filepath: str) -> str:
    """Read a file and return its contents."""
    with open(filepath, "r") as f:
        return f.read()

def write_file(filepath: str, content: str) -> None:
    """Write content to a file."""
    with open(filepath, "w") as f:
        f.write(content)

Simple. The agent needs to read code in and write code out.

Step 2: Send to Claude for Fixing

Now the core — send the code to Claude with clear instructions:

import anthropic

client = anthropic.Anthropic()  # Uses ANTHROPIC_API_KEY from environment

def fix_code(code: str, errors: str) -> str:
    """Send code to Claude and get the fixed version."""
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        messages=[
            {
                "role": "user",
                "content": f"""Fix the following Python code.

Here are the linting errors:
{errors}

Here is the code:
```python
{code}

Return ONLY the fixed Python code. No explanations. No markdown formatting. Just the code, ready to save to a file.""" } ] ) return response.content[0].text


The key is the prompt: tell Claude exactly what to fix and ask for ONLY the code back. No explanations, no markdown — just code you can write directly to a file.

**Tip:** AI models sometimes ignore the "no markdown" instruction and wrap the code in triple backticks. Add a cleanup function:

```python
def clean_response(text: str) -> str:
    """Remove markdown code fences if the model adds them."""
    text = text.strip()
    if text.startswith("```python"):
        text = text[len("```python"):].strip()
    if text.startswith("```"):
        text = text[3:].strip()
    if text.endswith("```"):
        text = text[:-3].strip()
    return text

Use it: fixed_code = clean_response(fix_code(code, errors))

Step 3: Run the Linter

The agent needs to check its own work. This is what separates an agent from a simple script — the verification step:

import subprocess

def run_linter(filepath: str) -> tuple[bool, str]:
    """Run the linter and return (passed, error_output)."""
    result = subprocess.run(
        ["python", "-m", "py_compile", filepath],
        capture_output=True,
        text=True
    )

    if result.returncode == 0:
        return True, ""
    else:
        return False, result.stderr

We use py_compile here because it checks basic Python syntax and needs no extra installation. For a real project, use ruff check (linting + style), mypy (type checking), or pytest (tests). The pattern is the same — run a command, check if it passed.

Security note: This agent runs commands on your machine. For production use, run it inside a Docker container or sandbox to prevent accidental damage.

Step 4: The Agent Loop

Now we combine everything into the agent loop — the read-fix-verify cycle:

def agent_fix(filepath: str, max_attempts: int = 3) -> bool:
    """
    The agent loop:
    1. Read the file
    2. Run the linter
    3. If errors found — send to Claude for fixing
    4. Write the fix
    5. Run the linter again
    6. If still broken — try again (up to max_attempts)
    """
    print(f"Agent starting on: {filepath}")

    for attempt in range(1, max_attempts + 1):
        print(f"\n--- Attempt {attempt}/{max_attempts} ---")

        # Read the current code
        code = read_file(filepath)

        # Run the linter
        passed, errors = run_linter(filepath)

        if passed:
            print(f"✓ Code passes linting!")
            return True

        print(f"✗ Linting errors found:\n{errors}")
        print("Sending to Claude for fixing...")

        # Send to Claude
        fixed_code = fix_code(code, errors)

        # Write the fix
        write_file(filepath, fixed_code)
        print("Fix applied.")

    # Check one final time after last attempt
    passed, errors = run_linter(filepath)
    if passed:
        print("✓ Fixed after final attempt!")
        return True

    print(f"✗ Could not fix after {max_attempts} attempts.")
    return False

This is the core pattern. Every AI coding agent — from Claude Code to Copilot Agent — uses this same loop. The real ones are more complex, but the idea is identical:

  1. Understand the current state
  2. Try to improve it
  3. Check if it worked
  4. Repeat if not

Step 5: Git Commit

If the fix works, commit it:

def git_commit(filepath: str, message: str) -> None:
    """Stage the file and create a git commit."""
    subprocess.run(["git", "add", filepath], check=True)
    subprocess.run(["git", "commit", "-m", message], check=True)
    print(f"Committed: {message}")

Step 6: Put It All Together

The complete agent:

#!/usr/bin/env python3
"""
A simple AI coding agent that fixes linting errors.

Usage:
    python agent.py path/to/file.py
"""

import subprocess
import sys
import anthropic

client = anthropic.Anthropic()


def read_file(filepath: str) -> str:
    with open(filepath, "r") as f:
        return f.read()


def write_file(filepath: str, content: str) -> None:
    with open(filepath, "w") as f:
        f.write(content)


def run_linter(filepath: str) -> tuple[bool, str]:
    result = subprocess.run(
        ["python", "-m", "py_compile", filepath],
        capture_output=True,
        text=True,
    )
    return (result.returncode == 0, result.stderr)


def fix_code(code: str, errors: str) -> str:
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        messages=[
            {
                "role": "user",
                "content": f"""Fix the following Python code.

Here are the linting errors:
{errors}

Here is the code:
```python
{code}

Return ONLY the fixed Python code. No explanations. No markdown. Just the code.""", } ], ) return response.content[0].text

def git_commit(filepath: str, message: str) -> None: subprocess.run([“git”, “add”, filepath], check=True) subprocess.run([“git”, “commit”, “-m”, message], check=True) print(f"Committed: {message}")

def agent_fix(filepath: str, max_attempts: int = 3) -> bool: print(f"Agent starting on: {filepath}")

for attempt in range(1, max_attempts + 1):
    print(f"\n--- Attempt {attempt}/{max_attempts} ---")

    code = read_file(filepath)
    passed, errors = run_linter(filepath)

    if passed:
        print("✓ Code passes!")
        return True

    print(f"✗ Errors found:\n{errors}")
    print("Asking Claude to fix...")

    fixed_code = fix_code(code, errors)
    write_file(filepath, fixed_code)
    print("Fix applied.")

passed, _ = run_linter(filepath)
if passed:
    print("✓ Fixed!")
    return True

print(f"✗ Could not fix after {max_attempts} attempts.")
return False

def main(): if len(sys.argv) < 2: print(“Usage: python agent.py ”) sys.exit(1)

filepath = sys.argv[1]

if agent_fix(filepath):
    git_commit(filepath, f"fix: auto-fix linting errors in {filepath}")
else:
    print("Agent failed. Manual fix needed.")
    sys.exit(1)

if name == “main”: main()


## How to Use It

Create a broken Python file to test:

```python
# broken.py
def hello(
    print("hello world")

def add(a, b)
    return a + b

class MyClass
    def __init__(self):
        self.value = 0

Run the agent:

python agent.py broken.py

The agent will:

  1. Read broken.py
  2. Find the syntax errors
  3. Send them to Claude
  4. Get the fixed code back
  5. Write the fix
  6. Verify the fix passes
  7. Commit the result

Making It Smarter

The agent above is simple. Here is how to make it more powerful:

Add a Planning Step

Real agents plan before they code. Add a planning call:

def plan_fix(code: str, errors: str) -> str:
    """Ask Claude to plan the fix before writing code."""
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=[
            {
                "role": "user",
                "content": f"""Analyze these errors and describe what needs to be fixed.
Be brief — just list the changes needed.

Errors:
{errors}

Code:
```python
{code}
```""",
            }
        ],
    )
    return response.content[0].text

Then use the plan in the fix step:

plan = plan_fix(code, errors)
print(f"Plan: {plan}")
fixed_code = fix_code(code, errors + "\n\nPlan:\n" + plan)

Add Test Running

Instead of just linting, run the full test suite:

def run_tests() -> tuple[bool, str]:
    result = subprocess.run(
        ["python", "-m", "pytest", "--tb=short", "-q"],
        capture_output=True,
        text=True,
    )
    return (result.returncode == 0, result.stdout + result.stderr)

Handle Multiple Files

Real agents work across many files. Track which files need changes:

def find_related_files(filepath: str) -> list[str]:
    """Find files that import the given file."""
    result = subprocess.run(
        ["grep", "-rl", f"import {filepath.replace('.py', '')}", "."],
        capture_output=True,
        text=True,
    )
    return result.stdout.strip().split("\n") if result.stdout else []

How This Compares to Real Agents

FeatureOur AgentClaude CodeCursor Agent
Read filesYesYesYes
Fix codeYesYesYes
Run testsBasicFull test suiteBasic
Plan before codingOptionalYesYes
Multi-file changesNoYesYes
Git integrationBasicFull (branches, PRs)Basic
Context windowSingle fileEntire projectOpen files
Retry on failureYes (3 attempts)Yes (unlimited)Yes

Our agent is 50 lines of core logic. Claude Code is thousands. But the fundamental loop is the same.

Key Lessons

1. The Loop is Everything

Read → Fix → Verify → Retry

Every successful agent follows this pattern. The quality comes from how good each step is.

2. Verification is What Makes It an Agent

A script that generates code is not an agent. An agent that generates code AND checks if it works IS an agent. The verification step is what makes the difference.

3. Clear Prompts = Better Results

“Fix this code” gives bad results. “Fix these specific linting errors and return only the code” gives great results. The more specific your prompt, the better the output.

4. Retry is Essential

AI is not perfect. Sometimes the first fix breaks something else. The retry loop handles that. Three attempts is usually enough for simple fixes.

What’s Next

You now understand how AI coding agents work from the inside. You can extend this agent to:

  • Fix multiple file types (Kotlin, TypeScript, Go)
  • Run test suites instead of just linting
  • Create pull requests on GitHub
  • Monitor a repo and fix issues automatically
  • Add a planning step for complex changes

Or you can skip building your own and use production agents like Claude Code or Cursor — which do all of this and much more.