Skip to main content
Building

How to build an agent that actually works

You built a skill or two and they work. Now you want something that runs on its own, without you sitting beside it. That is a different step entirely, and most teams fall down on the same five things.

The difference between a skill and an agent, in one sentence

A skill is something you invoke. An agent is something that decides.

That is the whole difference, and it is enormous. When you write a skill you fix the steps in advance: take input, do A, do B, return output. The model fills in a template. An agent has no template. It has a goal, a set of tools, and a loop. The model picks the next step itself, based on what it saw in the previous one. You are not writing the script. You are writing the conditions under which the script gets written.

And because that sounds exciting, people rush to build agents when they do not need one. My rule is simple: if you can write the steps on a piece of paper before the run starts, it is a skill. If step three depends on what happens in step two, only then is it an agent. Test yourself:

INTERACTIVESkill or agent? Pick a task

The loop: what actually happens on every turn

Every agent, however elaborate, runs the same four-step loop: it perceives the current context, decides what to do, acts through a tool, and observes the result. The result goes back into context, and the loop starts again. That is it. There is no magic beyond that.

Here is the part that matters: the model makes a decision once per turn, in the "decide" step. Everything else is plumbing you wrote. The quality of the agent is set by what the model sees at that moment: which tools are offered, how they are described, and what survived in context from earlier turns.

Step through the run below turn by turn. Then switch to "watch it break" - the exact same task, with one tool that is described badly. Notice the precise turn where things start to slide, and what happens to the context meter:

INTERACTIVEThe agent loop, turn by turn
Context0.0K tokens
The task: "Fix the test that broke in CI"
Press "Next turn" to see what happens on each pass.

What you see in the broken run is not "the model is stupid". It is that one bad first decision contaminated the context, and every decision after it was made on contaminated context. In agents, mistakes do not get replaced. They accumulate.

Tools: what to hand over and what to hold back

The instinct is to give the agent everything: search, files, terminal, browser, twenty tools, let it figure it out. In practice every extra tool is one more opportunity for a wrong choice. I have watched an agent with 4 sharp tools beat an agent with 15 general ones, over and over, on exactly the same tasks.

And the part everyone misses: the tool description is the real interface. Not its code. The model never sees the implementation. It sees a name, a description, and parameters. A vague description produces vague choices. Try the three tasks against both descriptions:

INTERACTIVESame tool, two descriptions
// vague description
search(query) // "searches stuff"
search("VAT calculation")
The model can't tell whether this searches the web or the code. It got Wikipedia articles about VAT.
// sharp description
code_search(query) // "Searches ONLY this repo's source. // Returns up to 20 matching lines // with file paths. Not a web search."
code_search("vat")
Obviously a code search. It got pricing.ts:42 on the first turn.

This is what a description that works looks like. It says what the tool does, what it does not do, and what comes back from it:

{
  "name": "code_search",
  "description": "Searches ONLY this repo's source code.
    Returns up to 20 matching lines with file paths.
    NOT a web search - for docs use fetch_docs.",
  "input": { "query": "string, a literal or regex pattern" }
}

Those three words, "NOT a web search", are worth more than any prompt patch you write afterwards. Describe a tool the way you would brief a new hire on day one: short, sharp, and clear about the boundaries.

Memory and context: what survives between turns

The agent does not "remember". It has a context window, and everything that enters it stays there until the space runs out. Every tool result, every error, every failed attempt piles up. After 20 turns the context of a typical agent is 80% waste: stale outputs that no longer matter, crowding out the things that do.

This is what kills long-running agents, and it has a name: context rot. The original task was written on turn #1, and by turn #30 it is either out of the window entirely or buried under 40K tokens of logs. The agent keeps running, it just no longer remembers what for.

Three rules that work for me:

  • Files, not context. Anything that has to survive gets written to a file (progress summary, decisions, what was tried and failed) and re-read at the start of each phase. Context is working memory, not an archive.
  • Truncate results. A tool that returns 3,000 log lines should return the 30 relevant ones. The truncation belongs in the tool, not in the model.
  • Repeat the task to itself. A short reminder of the goal and the stop condition gets injected every few turns, so it never falls out of the window.

Guardrails: what an agent never does alone

An agent without guardrails is not "autonomous", it is recklessness with an API key. Before you let anything run unsupervised, four things have to be in place: a turn ceiling, a token budget, a defined stop condition, and a human checkpoint on every irreversible action. Switch one off and see what it costs:

INTERACTIVEWhat happens when you switch a guardrail off?
Clean run
The agent fixed the bug in 9 turns, asked for approval once (before deleting an old branch), and stopped when the tests went green.
9turns
$0.42cost
Noirreversible damage?

Notice the asymmetry: the turn ceiling and the budget save money and time. But human approval is the only one that prevents irreversible damage. Deleting, force push, sending a customer email, deploying - all of those stop and ask. Always. Even when the agent is "sure". Especially when the agent is sure.

A good stop condition is external and measurable, not a feeling the model has:

// stop when ALL of these hold - not when the model "feels done"
stop_when:
  tests_pass: true          // measured, not self-reported
  turns_used: "<= 15"
  tokens_used: "<= 50_000"
  destructive_actions: 0    // anything else requires approval

Evals and traces: judge the path, not the answer

With a skill you check the output: input goes in, output comes out, right or wrong. With an agent that is not enough. An agent can reach the right answer down a horrifying path (30 turns, two files deleted by accident and restored), or fail politely after a flawless one. What you need to judge is the trajectory: the sequence of decisions, not just the ending.

And because agents are not deterministic, a single run tells you nothing. I run the same task 10 times before any change: if 8 succeed and 2 get stuck in a loop, I have a stability problem, not a capability problem, and the fix is completely different.

The habit works outside agents too. When I wanted to know which backend was faster in my transcription tool, I let the numbers decide instead of guessing. The answer was 5.81x, and I would not have guessed it right.

The most valuable tool in the box is reading a trace. When a run fails, the answer is always at one specific turn - the turn where a reasonable-looking decision sent everything to the wrong place. Open this trace turn by turn and try to find it before you press the button:

INTERACTIVEReading a trace: "Update the dependencies and open a PR"

Most people guess the failure is where the tests broke. It never is. It is always a few turns earlier, in a decision that looked sensible at the time. Reading traces is a skill, and the people who develop it fix agents ten times faster than the people who only look at the final answer.

That is the whole thing. A simple loop, a few sharp tools, context you manage, guardrails you do not compromise on, and judgement based on the path. An agent built that way is not perfect, but it works, and when it breaks you know exactly where.