Part 4: File tools
The coding agent can only change a project if we give it tools for reading and writing files. In this step we copy the Django template into a new project folder, then define the Python methods the model calls.
Copy the template
The notebook starts with an interactive helper:
import os
import shutil
def start():
project_name = input("Enter the new Django project name: ").strip()
if not project_name:
print("Project name cannot be empty.")
return
if os.path.exists(project_name):
print(f"Directory '{project_name}' already exists. Please choose a different name or remove the existing directory.")
return
shutil.copytree("django_template", project_name)
print(f"Django template copied to '{project_name}' directory.")
return project_name
Use it in the notebook:
project_name = start()
Some notebooks use a non-interactive version so repeated experiments are easier:
def start(project_name):
if not project_name:
print("Project name cannot be empty.")
return False
if os.path.exists(project_name):
print(f"Directory '{project_name}' already exists. Please choose a different name or remove the existing directory.")
return False
shutil.copytree("django_template", project_name)
print(f"Django template copied to '{project_name}' directory.")
return True
Call it with a folder name:
start("todo-two")
The important part is that every generated app gets its own copy. If the
agent makes a mess, the original django_template remains clean.
Tool class
The workshop code puts the tools in tools.py. The class takes the project
directory once, then every method works with paths relative to that directory.
from pathlib import Path
import os
import subprocess
class AgentTools:
SKIP_DIRS = {
".venv",
"__pycache__",
".git",
".pytest_cache",
".mypy_cache",
".coverage",
"node_modules",
".DS_Store",
}
def __init__(self, project_dir: Path):
self.project_dir = project_dir
Skipping generated and dependency folders keeps the file tree and searches
small. Otherwise the model wastes context on .venv, cache folders, and
irrelevant files.
Read and write files
The agent needs to read files before changing them:
def read_file(self, filepath: str) -> str:
"""
Read and return the contents of a file at the given relative filepath.
Parameters:
filepath (str): Path to the file, relative to the project directory.
Returns:
str: Contents of the file.
"""
abs_path = self.project_dir / filepath
try:
with open(abs_path, "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
return f"Error: file '{filepath}' not found."
It also needs to create and replace files:
def write_file(self, filepath: str, content: str) -> None:
"""
Write the given content to a file at the given relative filepath,
creating directories as needed.
Parameters:
filepath (str): Path to the file, relative to the project directory.
content (str): Content to write to the file.
Returns:
None
"""
abs_path = self.project_dir / filepath
abs_path.parent.mkdir(parents=True, exist_ok=True)
with open(abs_path, "w", encoding="utf-8") as f:
f.write(content)
These two methods let the coding agent edit code. The model chooses what to
read, decides what to change, and calls write_file with new content.
File tree
The model needs orientation before it can edit. see_file_tree gives it a
compact list of paths.
def see_file_tree(self, root_dir: str = ".") -> list[str]:
"""
Return a list of all files and directories under the given root directory,
relative to the project directory.
"""
abs_root = self.project_dir / root_dir
tree = []
for dirpath, dirnames, filenames in os.walk(abs_root):
for skip_dir in list(dirnames):
if skip_dir in self.SKIP_DIRS:
dirnames.remove(skip_dir)
for name in dirnames + filenames:
full_path = os.path.join(dirpath, name)
rel_path = os.path.relpath(full_path, self.project_dir)
tree.append(rel_path)
return tree
This is the equivalent of letting the agent look around the project before it starts editing. In the live run, the model used the tree plus file reads to discover the existing Django structure.
Bash command tool
The agent can run short validation commands, but it must not start the Django development server from the notebook. A server process keeps running and blocks the tool call.
def execute_bash_command(
self, command: str, cwd: str = None
) -> tuple[str, str, int]:
"""
Execute a bash command in the shell and return its output, error,
and exit code. Blocks running the Django development server.
"""
if "runserver" in command:
return (
"",
"Error: Running the Django development server (runserver) is not allowed through this tool.",
1,
)
After that guard, run the command with a timeout:
abs_cwd = (self.project_dir / cwd) if cwd else self.project_dir
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
cwd=abs_cwd,
timeout=15,
encoding="utf-8",
errors="replace",
)
return result.stdout, result.stderr, result.returncode
The timeout is part of the same safety idea. The tool should be useful for
checks like python manage.py makemigrations, not for long-running
interactive processes.
Search in files
The last tool is a simple grep. It returns file path, line number, and the matching line.
def search_in_files(
self, pattern: str, root_dir: str = "."
) -> list[tuple[str, int, str]]:
"""
Search for a pattern in all files under the given root directory.
"""
abs_root = self.project_dir / root_dir
matches = []
for dirpath, dirnames, filenames in os.walk(abs_root):
for skip_dir in list(dirnames):
if skip_dir in self.SKIP_DIRS:
dirnames.remove(skip_dir)
The method scans text files and skips files it cannot decode:
for filename in filenames:
filepath = os.path.join(dirpath, filename)
try:
with open(filepath, "r", encoding="utf-8") as f:
for i, line in enumerate(f, 1):
if pattern in line:
rel_path = os.path.relpath(filepath, self.project_dir)
matches.append((rel_path, i, line.strip()))
except Exception:
continue
return matches
This is enough for the workshop agent. A production coding agent would also need patch-based edits, better command isolation, streaming logs, and stronger safety rules.
Try the tools
Import the class and point it at the copied project:
from pathlib import Path
import tools
project_path = Path(project_name)
agent_tools = tools.AgentTools(project_path)
List files:
agent_tools.see_file_tree()
Read a file:
agent_tools.read_file("myproject/urls.py")
If these calls work, the agent can use the same methods through tool calling.
Continue with Part 5: Developer prompt.