Project 2: Autonomous Coding Agent¶
Project Overview¶
Build an AI-powered coding agent that can: - Analyze a codebase to understand structure and dependencies - Identify bugs and potential issues - Suggest and apply fixes - Run tests to verify changes - Create git commits with descriptive messages
Time estimate: 15-20 hours Skills used: AI Agents, Tool Use, ReAct Pattern, Code Analysis
Architecture¶
User: "Fix the failing tests in src/utils.py"
|
v
[Agent Loop (ReAct)]
|
|-- THINK: "I need to understand what tests are failing"
|-- ACT: run_tests() -> see failures
|
|-- THINK: "Test X fails because function Y has a bug"
|-- ACT: read_file("src/utils.py") -> see the code
|
|-- THINK: "The bug is on line 42, off-by-one error"
|-- ACT: edit_file("src/utils.py", line=42, new_code=...)
|
|-- THINK: "Let me verify the fix"
|-- ACT: run_tests() -> all pass
|
|-- THINK: "Tests pass. I'll commit the fix."
|-- ACT: git_commit("Fix off-by-one error in utils.py")
v
Done: "Fixed the bug. All tests pass."
Step 1: Define Agent Tools¶
import subprocess
import os
import json
class CodingTools:
"""Tools available to the coding agent."""
def __init__(self, repo_path: str):
self.repo_path = repo_path
def read_file(self, filepath: str) -> str:
"""Read the contents of a file."""
full_path = os.path.join(self.repo_path, filepath)
if not os.path.exists(full_path):
return f"Error: File {filepath} does not exist"
with open(full_path, "r") as f:
lines = f.readlines()
# Return with line numbers
return "".join(f"{i+1:4d} | {line}" for i, line in enumerate(lines))
def list_files(self, directory: str = ".") -> str:
"""List files in a directory."""
full_path = os.path.join(self.repo_path, directory)
if not os.path.isdir(full_path):
return f"Error: {directory} is not a directory"
entries = []
for item in sorted(os.listdir(full_path)):
item_path = os.path.join(full_path, item)
prefix = "[DIR]" if os.path.isdir(item_path) else "[FILE]"
entries.append(f" {prefix} {item}")
return "
".join(entries)
def edit_file(self, filepath: str, old_text: str, new_text: str) -> str:
"""Replace old_text with new_text in a file."""
full_path = os.path.join(self.repo_path, filepath)
if not os.path.exists(full_path):
return f"Error: File {filepath} does not exist"
with open(full_path, "r") as f:
content = f.read()
if old_text not in content:
return f"Error: Could not find the specified text in {filepath}"
content = content.replace(old_text, new_text, 1)
with open(full_path, "w") as f:
f.write(content)
return f"Successfully edited {filepath}"
def run_tests(self, test_path: str = "") -> str:
"""Run tests and return results."""
cmd = ["python", "-m", "pytest", "-v"]
if test_path:
cmd.append(test_path)
result = subprocess.run(
cmd, cwd=self.repo_path,
capture_output=True, text=True, timeout=60
)
output = result.stdout + result.stderr
# Truncate if too long
if len(output) > 3000:
output = output[:1500] + "
... (truncated) ...
" + output[-1500:]
return output
def run_command(self, command: str) -> str:
"""Run a shell command (limited to safe commands)."""
safe_prefixes = ["python", "grep", "find", "cat", "wc", "git diff", "git log"]
if not any(command.startswith(p) for p in safe_prefixes):
return "Error: Command not allowed for safety"
result = subprocess.run(
command, shell=True, cwd=self.repo_path,
capture_output=True, text=True, timeout=30
)
return result.stdout + result.stderr
def git_commit(self, message: str) -> str:
"""Stage all changes and commit."""
subprocess.run(["git", "add", "-A"], cwd=self.repo_path)
result = subprocess.run(
["git", "commit", "-m", message],
cwd=self.repo_path, capture_output=True, text=True
)
return result.stdout + result.stderr
Step 2: Build the ReAct Agent Loop¶
from openai import OpenAI
client = OpenAI()
SYSTEM_PROMPT = """You are an expert coding agent. You analyze code, find bugs, and fix them.
You have access to these tools:
- read_file(filepath): Read a file's contents with line numbers
- list_files(directory): List files in a directory
- edit_file(filepath, old_text, new_text): Replace text in a file
- run_tests(test_path): Run tests and see results
- run_command(command): Run safe shell commands
- git_commit(message): Commit all changes
Use the ReAct pattern:
1. THINK: Reason about what to do next
2. ACT: Call a tool with specific arguments
3. OBSERVE: Read the tool result
4. Repeat until the task is complete
When you're done, respond with DONE: followed by a summary.
Always:
- Read the failing test first to understand what's expected
- Read the source code before making changes
- Make minimal, targeted fixes
- Run tests after each change to verify
- Commit with a clear, descriptive message"""
def run_agent(task: str, tools: CodingTools, max_steps: int = 15):
"""Run the coding agent on a task."""
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": task}
]
tool_definitions = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read the contents of a file with line numbers",
"parameters": {
"type": "object",
"properties": {"filepath": {"type": "string"}},
"required": ["filepath"]
}
}
},
{
"type": "function",
"function": {
"name": "list_files",
"description": "List files in a directory",
"parameters": {
"type": "object",
"properties": {"directory": {"type": "string", "default": "."}},
}
}
},
{
"type": "function",
"function": {
"name": "edit_file",
"description": "Replace old_text with new_text in a file",
"parameters": {
"type": "object",
"properties": {
"filepath": {"type": "string"},
"old_text": {"type": "string"},
"new_text": {"type": "string"}
},
"required": ["filepath", "old_text", "new_text"]
}
}
},
{
"type": "function",
"function": {
"name": "run_tests",
"description": "Run pytest tests",
"parameters": {
"type": "object",
"properties": {"test_path": {"type": "string", "default": ""}},
}
}
},
{
"type": "function",
"function": {
"name": "git_commit",
"description": "Stage and commit all changes",
"parameters": {
"type": "object",
"properties": {"message": {"type": "string"}},
"required": ["message"]
}
}
}
]
for step in range(max_steps):
response = client.chat.completions.create(
model="gpt-4.1",
messages=messages,
tools=tool_definitions,
)
msg = response.choices[0].message
messages.append(msg)
# Check if agent is done
if msg.content and "DONE:" in msg.content:
print(f"Agent completed in {step + 1} steps")
print(msg.content)
return msg.content
# Execute tool calls
if msg.tool_calls:
for tool_call in msg.tool_calls:
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
print(f" Step {step + 1}: {fn_name}({fn_args})")
# Call the appropriate tool
tool_fn = getattr(tools, fn_name, None)
if tool_fn:
result = tool_fn(**fn_args)
else:
result = f"Error: Unknown tool {fn_name}"
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
return "Agent reached maximum steps without completing"
Step 3: Usage Example¶
# Initialize tools for a repository
tools = CodingTools(repo_path="/path/to/your/project")
# Run the agent
result = run_agent(
task="The tests in tests/test_utils.py are failing. Read the test file and the source code, identify the bugs, fix them, verify the tests pass, and commit your changes.",
tools=tools
)
Evaluation¶
Test your agent on these scenarios:
| Scenario | Description | Success Criteria |
|---|---|---|
| Simple bug fix | Off-by-one error in a loop | Tests pass after fix |
| Missing import | Function uses unimported module | Tests pass, clean import |
| Logic error | Wrong conditional operator | Tests pass, minimal change |
| Type error | String/int confusion | Tests pass, type-correct |
Extension Ideas¶
- Add a
search_codetool for grep/ripgrep across the codebase - Implement multi-file refactoring support
- Add GitHub API integration to create PRs
- Build a web UI for interacting with the agent
- Add memory so the agent remembers past fixes and patterns
Resources¶
- OpenAI Function Calling Guide: Tool use with GPT models
- ReAct Paper: "ReAct: Synergizing Reasoning and Acting in Language Models"
- LangChain Agents: Framework for building tool-using agents
- Aider: Open-source AI pair programming tool for reference
Next Project¶
Project 3: Multi-Agent Research System — Build a team of AI agents that collaborate to research topics, write reports, and fact-check each other.