I thought it might be useful to talk about what exactly an agent is, because there’s a lot of mystery hiding behind a fairly straightforward idea. Once you understand that idea, you’ll better understand how tools like Claude Code work and start to see how you could build your own. (View Highlight)
To understand what an agent is, you first need to understand what a tool call is. And to understand a tool call, you need to understand the basic human <-> LLM conversational loop. So we’ll start there and then work our way back up. (View Highlight)
Conversation basics
A conversation with an LLM is a sequence of HTTP requests and HTTP responses: you say something then the LLM responds. LLMs (like pretty much all modern web APIs) communicate with JSON. So when you send a request to Claude (my personal favourite LLM), the request body includes some JSON (View Highlight)
We call each of these two messages a turn. Turns always come in pairs and always happen in the same order: user then assistant. The LLM will always listen to everything you have to say, and only then respond. (This is something we can all strive for in our own conversations 😆.) (View Highlight)
It’s worth noting that the LLM API is stateless. That means if we continue this conversation, we have to resend the entire conversation so far (View Highlight)
This is why token costs grow over the course of a conversation; each new request has to include the contents of all the previous turns. (View Highlight)
Things get more complicated when we introduce tools. Tools are a way for LLMs to break free from their limits: they allow LLMs to get data about the world as it is today and allow them to take actions. But what exactly is a tool? A tool is just a different name for a function, but importantly it’s a function that’s run on your computer. (View Highlight)
Now we can use the tool to allow Claude to answer a question that cannot be part of its training data: today’s date. So our request includes a description of that tool: (View Highlight)
This is a request for you to do some work, i.e. call the today() function with no arguments. This workflow wouldn’t be very appealing if you personally had to call this function, which is where the harness comes in. A harness is a program that wraps around the raw LLM and can (among other things) run these local functions for you. Harnesses include the web chat interface, more complicated tools like Claude Code or Codex, and in this case, ellmer. (View Highlight)
So now ellmer takes over, sending a new message back to the assistant that contains the results of calling today(). It also includes the complete prior conversational history, but I’ve omitted that to stay focused on what’s changed: (View Highlight)
(The role here is a little confusing; it would be clearer to say that this response was generated by the harness, rather than the user.)
Now that Claude knows what day it is, it can respond to our initial query: (View Highlight)
We call the complete sequence of human, harness, and LLM turns a round, which in this case consists of four turns/two pairs (human -> LLM, harness -> LLM). (View Highlight)
With all these pieces in place we can now define an agent. An agent is an LLM, in a harness, that calls tools repeatedly in a loop, deciding each next step from the last result. Most agents have two types of tools: read tools that can observe the world, and write tools that can change the world. This combination leads to a natural iterative cycle where the LLM does some initial exploration (read), makes a change (write), observes the result of the change (read), etc etc. That’s why our example above wasn’t an agent; there’s no need for iteration.
So now let’s make a real, if simple, agent. The goal of this agent is to help you delete files. So we give it a read tool that lists the files in the current directory and a write tool that deletes files: (View Highlight)
I won’t show the full sequence of JSON requests and responses here because it’s a bit long, but in summary it looks like this:
• User: Delete all the csv files in the current directory.
• Claude: Run the ls() tool
• ellmer: a.csv, b.csv, a.R, b.R, c.R
• Claude: [Run rm("a.csv"), Run rm("b.csv")]
• ellmer: [TRUE, TRUE]
• Claude: I’ve deleted two csv files for you.
This is one round made up of six turns/three pairs (human-LLM, harness-LLM, harness-LLM). (View Highlight)
Now let’s talk about the elephant in the room: I just gave an LLM the ability to delete files on my computer! Hopefully you find this a little worrying, as we know that LLMs are never 100% trustworthy.
This is one of the biggest challenges of agents. An agent isn’t useful unless it can take actions on your behalf, but how do you know it’s always taking the right actions? This is one of the biggest questions posed by AI agents. There are some things you can do locally to protect yourself when the agent is scoped to actions on your computer, like sandboxing, using git, and making backups. But what if actions are in the real world like sending emails or spending money? That feels high risk to me! (View Highlight)