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:
- Reads a Python file
- Sends it to Claude with instructions to fix linting errors
- Writes the fixed code back to the file
- Runs the linter to verify the fix
- If it still fails — tries again (up to 3 attempts)
- 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:
- Python 3.10+
- An Anthropic API key (get one at console.anthropic.com)
pip install anthropic
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:
- Understand the current state
- Try to improve it
- Check if it worked
- 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
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:
- Read
broken.py - Find the syntax errors
- Send them to Claude
- Get the fixed code back
- Write the fix
- Verify the fix passes
- 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
| Feature | Our Agent | Claude Code | Cursor Agent |
|---|---|---|---|
| Read files | Yes | Yes | Yes |
| Fix code | Yes | Yes | Yes |
| Run tests | Basic | Full test suite | Basic |
| Plan before coding | Optional | Yes | Yes |
| Multi-file changes | No | Yes | Yes |
| Git integration | Basic | Full (branches, PRs) | Basic |
| Context window | Single file | Entire project | Open files |
| Retry on failure | Yes (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.
Related Articles
- What Are AI Coding Agents? — understand agents before building one
- How to Set Up Claude Code — use a production agent instead of building your own
- What is Vibe Coding? — how agents enable vibe coding workflows
- 7 Best Free AI Coding Tools — free tools to experiment with