Most people start an agent project by choosing a framework. We think that is backwards. An agent is a small program that repeats three things: look, decide, act. Everything else is plumbing around that loop.
The whole idea in 15 lines#
Here is a complete agent, with no framework. It asks the model what to do, runs a tool, feeds the result back, and stops when the model says it is done or when it runs out of steps.
type Action = { type: 'tool'; name: string; input: unknown } | { type: 'done'; result: string }export async function run(task: string, tools: Tools, maxSteps = 8) { const history: Message[] = [{ role: 'user', content: task }] for (let step = 0; step < maxSteps; step++) { const action: Action = await model.next(history) if (action.type === 'done') return action.result const output = await tools[action.name](action.input) // can throw: that's fine history.push({ role: 'tool', name: action.name, content: JSON.stringify(output) }) } throw new Error(`Gave up after ${maxSteps} steps`)}Tip
Write the stop condition before you write the first tool. An agent that cannot stop is the most expensive bug you will ship.
What the model can see#
The model only knows what is in history. That makes the context the most important design decision you have. Put in the task, the tool results and a short description of each tool. Leave out everything else until you can prove it helps.
Name tools like functions#
Models pick tools the way you pick functions from an unfamiliar library: by name and by description. search_orders beats helper_2 every time. Keep inputs small and typed, and return small, boring results.
Make failures loud#
When a tool fails, do not swallow it. Throw, log it, and let the model see a short, honest error. Quiet failures turn into confident wrong answers.
Decide how you will know it is wrong#
Before you add a second tool, write down five tasks and the answer you expect for each. Run them after every change. It is not glamorous, and it is the difference between a demo and a product.
The model is never the bug you can see.
Once the loop is stable, frameworks start to make sense, because you know which parts of them you actually need.