DEV Community

Cover image for Harness Engineering - Part 4: The Tool Layer
Fikayo Adepoju
Fikayo Adepoju

Posted on

Harness Engineering - Part 4: The Tool Layer

Welcome back to the Harness Engineering series — a 10-part journey from raw language model to production-ready agentic system. Made by builders. For builders.

In Part 3, we looked at the Loop — the outermost machinery of a harness, the piece that drives everything else. But a Loop on its own is a hollow shell. It calls the model. The model responds. And then, if the model wants to affect anything outside the text it just produced, it needs to reach for a tool.

That's what this article is about: the Tool Layer. The set of functions the harness makes available to the model, and the design decisions that separate a tool surface a model can actually use from one that constantly frustrates it.

What's ahead:

  1. Part 1: The Raw Model Problem
  2. Part 2: Defining the Harness — The Six Components
  3. Part 3: The Control Loop
  4. The Tool Layer ← You are here
  5. Part 5: Context Engineering
  6. Part 6: The Filesystem & Environment
  7. Part 7: The Memory Layer
  8. Part 8: Observability
  9. Part 9: The Harness Architecture
  10. Part 10: Decomposing Claude Code

By the end of this article, you'll know what tools actually are, why they exist, and — more usefully — how to tell a well-designed tool surface from a badly-designed one the moment you look at one.

Let's get started.


📚 Want to go deeper than the articles?

While you follow along with this series, I've put together two hands-on resources that go further than any single article can:

Both are optional — the series stands on its own. But if you want the full studio-quality version, that's where it lives.


What The Tools Are

The Tools are the set of functions the harness exposes to the model.

Concretely: the harness tells the model "here are the functions you can call, here are their names, here are the parameters they take, here's what they do." Then on any given turn, the model can decide to emit a structured request to call one of them. Something like:

{
  "tool": "read_file",
  "parameters": {
    "path": "/etc/hosts"
  }
}
Enter fullscreen mode Exit fullscreen mode

The harness sees this request in the model's response, runs the actual function (in this case, opens /etc/hosts and reads its contents), and feeds the result back into the model on the next Loop iteration.

That's the whole mechanic. Two-way traffic: the model requests, the harness executes, the harness returns.

If you're coming from traditional software, think of the Tool Layer as the API the harness offers to the model — the same way an operating system offers system calls to a running program. Same shape, different consumer.

Why The Tools Exist

Tools are the model's surface area into the world. Without them, the model produces text and that text goes nowhere. With them, the model can decide to do a thing and have the thing actually happen.

Look back at Part 1 — the raw model problem. Three of the five gaps we named there close through tools:

  • No execution → tools that run code, touch files, hit APIs
  • No fresh knowledge → tools that search, retrieve, or query external data
  • No environment → tools are how the model reaches into whatever environment the harness sets up

(The other two gaps — persistence and verification — are addressed by different components, which we look at in later parts.)

Every capability you've ever seen an agent exercise came through a tool. When Claude Code writes a file, that's an edit tool. When a research agent searches the web, that's a search tool. When a customer-support agent looks up an order, that's a get_order tool. The Loop drives the cycle; the Tools are what the model asks for inside each turn.

What a Good Tool Design Looks Like

If you look at ten agent frameworks, you'll see ten different opinions on how many tools an agent "needs" and how they should be shaped. The good ones share three properties.

A Small Set That Composes Well

There's a design failure mode where every use case gets its own bespoke tool. You end up with fifteen or fifty tools:

  • read_python_file
  • read_javascript_file
  • read_config_file
  • list_files_in_directory
  • list_files_matching_pattern
  • run_python_script
  • run_shell_script
  • …and on it goes

That surface is almost always worse than two well-named tools:

  • read_file(path)
  • bash(command)

You can express everything in the first list using the second — and a great many things the first list doesn't cover. The model doesn't have to memorize fifteen names and their subtle differences; it just has to think "I want to read this thing" or "I want to run this command."

The rule of thumb: compose, don't enumerate. A small set of sharp, general-purpose tools almost always outperforms a large set of specialized ones. Not always — sometimes a specific business action deserves its own named tool — but as a default posture, composition wins.

Clear, Structured Outputs — Including Errors

The model has to be able to tell success from failure from the tool result alone. It doesn't get to inspect your logs. It doesn't get to look at your stack traces. Whatever came back in the tool result is all the information it has to work with.

Which means:

  • Success and failure need to look different, unambiguously.
  • Errors should be structured, not just "Something went wrong."
  • Whenever possible, errors should hint at how to recover.

A tool that returns null on both "file not found" and "file is empty" is a tool the model can't reason about. A tool that returns {"error": "file_not_found", "path": "/foo/bar"} for one case and {"content": ""} for the other is a tool the model can actually use to correct course.

Tools Sized So The Model Can Use Them Well

There's a Goldilocks zone for tool granularity, and it's easy to miss on both sides.

Too small: each tool does a trivial thing, so the model needs twenty tool calls to accomplish what could have been one. You spend money, you spend time, and — worse — you spend context. Every tool call bloats the conversation with request-and-result pairs the model then has to keep re-reading.

Too big: a single tool does five things internally, so when it fails you can't tell which of the five failed. The model gets back "operation failed" and has no way to isolate the problem. It'll usually respond by trying the same thing again, or by giving up entirely.

The right size is: each call does enough meaningful work to be worth the round trip, but each failure is diagnosable. If you look at a tool's design, imagine the model getting an error from it, and can imagine the model figuring out what to do next — the tool is probably right-sized. If you look at it and think "the model would just be confused," it's probably wrong-sized in one direction or the other.

Example: Claude Code

Claude Code's tool surface is famously small: read, edit, bash, glob, grep, plus a handful of others. That's it. No refactor_python_function tool. No run_pytest_and_summarize tool. No open_pr_with_generated_summary tool.

Compare that to an agent framework that ships with fifty specialized tools out of the box.

The Claude Code design bet is that a competent model with sharp, general-purpose tools beats a constrained model with many specialized ones. That's a tools-design decision, and it shapes the entire feel of the product. When Claude Code needs to do something novel — refactor across files, run a specific test harness, generate a summary of a diff — it doesn't need a new tool. It composes what it already has: bash to run the test, grep to find the failing pattern, read to look at the file, edit to fix it.

When Claude Code can't do something well, the fix is usually not "add more tools." The fix is usually somewhere else in the harness — better context, better memory, better prompting. The tool surface stays lean on purpose.

Where This Leaves Us

The Tools are the harness's contract with the model. They define what the model is allowed to request. They don't decide when the model gets to request them (that's the Loop, from Part 3), and they don't decide what state the model has when deciding what to request (that's coming in Part 5, on Context). But the shape of the tool surface constrains everything downstream.

A great Loop with a badly-designed tool set produces an agent that flails. A modest Loop with a well-designed tool set produces an agent that composes its way to solutions. Tools are that important.


Remember that this article is part of a longer 10-part series that walks you through every component of an agentic harness.

Here's the roadmap:

  1. Part 1: The Raw Model Problem
  2. Part 2: Defining the Harness — The Six Components
  3. Part 3: The Control Loop
  4. The Tool Layer ← You just finished this one.
  5. Part 5: Context EngineeringMove to this one.
  6. Part 6: The Filesystem & Environment
  7. Part 7: The Memory Layer
  8. Part 8: Observability
  9. Part 9: The Harness Architecture
  10. Part 10: Decomposing Claude Code

See you in the next one.

Happy coding :)

Top comments (0)