Skip to main content

An agent that builds its own tools

Submitted by Lennart on

Most AI agents have a fixed toolbox, written by a human in advance. Yesterday, I built one where the agent writes the tools itself, and where each tool is kept for the next task. Everything it does can be audited afterward.

When building an AI agent today, you give it a set of tools: look up in the calendar, search the CRM, send an email. The model chooses between them, but it cannot create new ones. If a tool is missing, a human must write it.

The second scenario is even worse: The agent writes a Python script on the fly, runs it, and discards it. This is how many code agents and data analysis functions in chatbots work. The next time the same task comes up, it writes a new script, slightly different from the last. Nothing is reused, nothing is tested, and afterward, no one can say which code produced the number in the report. You pay for the model to invent the same tool repeatedly, and each time it's a new place it can go wrong.

My experiment reverses this. The agent, here DeepSeek, is given a task and writes a small program itself to solve it. The program is registered, it runs immediately, and it doesn't disappear when the task is solved. It's ready for the next time, for you, for other programs, and for the agent itself.

Code Lives in the Log, Not a File

Beneath the agent is cross.stream, or xs for short. It's an event log: a long series of things that have happened, in the order they occurred. Nothing is overwritten. New entries are only appended to the bottom.

What's special about xs is that code is also an event. If you write Nushell code for a topic ending in .create, the code is registered and runs. There's no file to save, no server to start, and nothing to install. When the machine restarts, xs reads the log again and starts all valid programs.

This means the agent can extend the system it runs on. It writes an event, and then a new tool exists.

Tools Built on Tools

Alongside the programs are shared modules: small libraries of functions that programs can import. When the agent needs to write something new, it's given the source code of all modules in its task, so it knows what already exists.

Modules can be inspected like anything else. The site shows each module with its source code and versions, and a click puts the code into an edit field. A human can thus write a module or correct one, and the new version also becomes an event in the log. A program already running will stick to the module version it was registered with until it's updated. A new module therefore cannot quietly change the behavior of something already running.

It works. I asked for an actor that maintains a running sum and only adds valid numbers. DeepSeek wrote it and imported program-lib itself, a module with the require-number function, rather than writing the number check again. It reused a tool that was already there.

The inventory is another example. It's an actor with reservations and shipments: you add ten, reserve four for one order, try to reserve seven for another, and get a no because only six are left. I asked the agent to improve it, and it wrote a new version that handles a shipment of a reservation that doesn't exist, rather than crashing. It built upon its own work.

The point is that the toolbox grows. Each task can leave behind a tool, and the next task can use it. And once a tool is written, it's code, not a language model, that responds. It costs nothing to run, it gives the same answer every time, and it can be tested.

Today, the agent sees the modules when it writes new code. The next step is to give it the list of its own completed programs, so it can call them too. The design makes this a small step, because the list is just a query on the log.

An Audit Trail You Don't Have to Remember to Keep

This is where the design truly pays for itself. When an agent writes its own code, the first question is: What did it actually do? Here, the answer is always in the log, because everything is an event:

  • My task is an event: agent.call.
  • The code the model wrote is an event with a description and a link to the task.
  • Whether the system accepted or rejected the code is an event.
  • Each call of the program is an event, as is the response or error.
  • A new version points to the previous one and to the bug it was supposed to fix.

After a day, there are 416 events and nine programs. I can see exactly what I asked for, what the model responded with, which version is running, and what each individual call produced. The page at 127.0.0.1:3031/agent makes it possible to inspect everything: the source code of each program, the entire version history, the actor's latest state, and the code in each shared module that the programs import. Nothing runs without me being able to read it, and that includes the tools that the tools build upon.

This isn't a log someone had to remember to keep. It is the system. The programs only exist because they are in the log, so no untraceable program can be found.

When Code Fails

I asked for a program that should remember a list. DeepSeek wrote code that read from $frame.stream, a field that doesn't exist. It was pure invention. It looked like something that should exist, and that's precisely how language models go wrong.

The program was registered, and my first call failed with "Cannot find column 'stream'". The error became an event with the exact line and column.

Then something happened that I didn't do myself. Another actor, auto-repair, monitors all errors in the log. It sent the failing code and the error message back to the model and asked for a correction. The new version was written as a new event with a link to the old one and to the error. There's a cap of two corrections per error, so a model and an error can't keep each other running all night.

And here the audit trail showed its value. The correction removed the error, but also part of the intent. The new version returns a list with the single element I send. It remembers nothing. It works, but it doesn't do what I asked. I could only see that because both versions, the task, and the error lay side by side. A system that corrects its own errors optimizes for the error disappearing. That's not the same as the task being solved, and therefore a human needs to be able to look.

Potential for an SME

Tacit knowledge becomes tools. An SME runs on small rules that live in people's heads: The inventory must not go negative. Urgent tasks come first. A payout over a certain amount requires two signatures. Here, each rule can be described in a sentence and become a tool that runs, can be reused, and won't retire.

The agent becomes cheaper the more it's used. The first time a task is solved, the model thinks. The second time, code runs. An agent that builds its own tools shifts work from expensive and unpredictable model calls to cheap and predictable code.

Traceability is free. If an auditor or a board asks, "Why did the system do this on October 7th?", the answer isn't a shrug. You play the log forward to that day and see it: the task, the code, the version, and the call.

Data outlives code. The calls are what endure, while an actor's state is calculated by playing the calls through, just as a balance is the sum of the postings. A better version of the inventory can play the same history through and arrive at the new answer.

What Needs to Be in Place First

This runs on my own machine, and there's a reason for that.

There is no sandbox. The prompt asks the model to only write pure calculations without network, files, or external commands. But an instruction in a prompt is a wish, not a wall. The code runs with the server's permissions. The audit trail makes it possible to see what happened. It doesn't prevent it.

Green light doesn't mean correct. The list example shows this. Someone needs to read the code, at least before a tool is allowed to touch anything that matters.

Replay has requirements. Because an actor's history is played through on each startup, it must not do anything in the world along the way. If it sends an email with each call, it will send them all again after a restart.

These are the questions any business should ask before letting AI write anything that runs: Who reads the code? What does it have access to? And can we see afterward what happened? Most AI systems answer the last one poorly. Here, it's what the entire design is built around.