Build A Basic AI Agent From Scratch: Tools
31 May 2026<br>Build A Basic AI Agent From Scratch: Tools
58 minute read<br>Artificial Intelligence
In the previous part of the Build A Basic AI Agent From Scratch series, we built the most basic AI agent harness possible.<br>It was just a connection to a model, a way to take user input, a store of context of the conversation and a loop that kept the agent running.
Of course, this agent is not very useful. It can only interact by taking your input and answering you based on its internal knowledge.<br>If we want our agent to be more useful and do work in behalf of us, we have to give it a way to give it some way to take actions in its environment. In this case, the computer it's running on. The way you allow an agent to take actions in your computer is with tools .
What are Tools?
A tool is a program or function that you expose to your LLM to allow it to invoke it autonomously. A tool can be as simple as a Python function implemented in the same agent code and as complex as an MCP (Model Context Protocol) server that does a HTTP request to an API that reads or updates a database.
Note: MCP is not covered in this part of the series but it will be covered in the future.
How do Agents use Tools?
Large Language Models output text, so how can they use tools? The first implementations of tool calling relied on suggesting the LLM to<br>output a text like Action: web_fetch and then the agent harness parsing the text output and running the function. This was a bit unreliable, since the model sometimes didn't exactly follow the format we were expecting.
Modern LLMs already have native tool calling baked into them to make this more reliable. These models are fine-tuned to produce JSON structured tool requests. This native implementation has built-in validation, which minimizes hallucinations and makes the agent more reliable when it has to invoke a tool.
Improving our Agent with Tools
We will be building on our previous basic agent we already built in the last part of this series: Build A Basic AI Agent From Scratch.
We will start by implementing the most basic tools an AI agent needs to take action. These tools are usually built-in the most common agent harnesses. All of them are simple, but essential and powerful.
In the previous Python code, we will create a tools submodule. Here we will implement all our tools and their schemas.
First, let's start with the bash tool:
def run_bash(command: str) -> str:<br>"""Run a bash command and return its output."""<br>result = subprocess.run(<br>command, shell=True, text=True, capture_output=True<br>output = result.stdout<br>if result.stderr:<br>output += f"\nSTDERR:\n{result.stderr}"<br>return output or "(no output)"<br>This is the most powerful tool. Allowing our agent to run bash commands will let it do anything on the computer it's running on. On one hand, this is good because it relieves us from implementing a tool for each program that can just be run using bash and that the LLM already knows how to use. On the other hand, this is the most dangerous tool (also because it will let it do anything on the computer it's running on). In future parts of this series we will crack down on security so this doesn't become a liability.
The next tool is the read file tool:
def read_file(path: str, offset: int = 1, limit: int = 200) -> str:<br>"""Read lines from a file, with optional offset and limit."""<br>p = Path(path)<br>if not p.exists():<br>return f"Error: file not found: {path}"<br>lines = p.read_text(errors="replace").splitlines()<br>selected = lines[offset - 1: offset - 1 + limit]<br>return "\n".join(f"{offset + i}: {line}" for i, line in enumerate(selected))<br>This allows our agent to read the files on the computer. This is useful for many cases, like for example reading all the files in our codebase for coding agents.
The next tool is the glob files tool:
def glob_files(pattern: str, path: str = ".") -> str:<br>"""Find files matching a glob pattern inside a directory."""<br>matches = glob_module.glob(f"{path}/**/{pattern}", recursive=True)<br>matches += glob_module.glob(f"{path}/{pattern}")<br>unique = sorted(set(matches))<br>return "\n".join(unique) if unique else "(no matches)"<br>This tool can be used to find files in a directory. Obviously needed so the agent can explore your computer and see which files are available before it reads them.
The next tool is the grep tool:
def grep(pattern: str, path: str = ".", include: str = "*") -> str:<br>"""Search file contents for a regex pattern, optionally filtering by filename glob."""<br>results = []<br>for filepath in glob_module.glob(f"{path}/**/{include}", recursive=True):<br>fp = Path(filepath)<br>if not fp.is_file():<br>continue<br>try:<br>for i, line in enumerate(fp.read_text(errors="replace").splitlines(), 1):<br>if re.search(pattern, line):<br>results.append(f"{filepath}:{i}: {line}")<br>except OSError:<br>pass<br>return "\n".join(results) if results else "(no matches)"<br>This tool searches file contents using regular expressions and returns matching lines together with their...