Learning · Chapter 3 of 4

Give Your Agent a Tool

Let a Maki agent call a tool: load the web_search plugin, ask for Hacker News stories, and learn how the agent decides when to use it.

Last tested on 2026-11-12 · model qwen3.5:4b

In chapter 2 the agent answered from what the model already knew. Here it gets a tool, so it can fetch something new. The tool is the web_search plugin that ships with Maki.

The whole program

This is ch03_search_tool/scout.py. Run it with python ch03_search_tool/scout.py rust, or give it any topic.

"""Chapter 3: give Scout a tool.

Run it with:  python ch03_search_tool/scout.py "python"
The web_search plugin needs no API key. Its search_hackernews method looks at
Hacker News stories from the current week.
"""
import os
import sys

from maki import init_backend
from maki.agents import Agent

DEFAULT_SPEC = "ollama:qwen3.5:4b"   # 3.4 GB. Bigger machine: "ollama:gemma4:26b".


def make_backend(spec=None):
    """The model Scout talks to, named by a "provider:model" string.

    Set SCOUT_BACKEND to use another server, for example "llamacpp" or
    "vllm:Qwen/Qwen2.5-1.5B-Instruct". Nothing else in the program changes.
    """
    spec = spec or os.environ.get("SCOUT_BACKEND", DEFAULT_SPEC)
    # think=False is an Ollama setting (explained in the chapter); other backends lack it.
    options = {"think": False} if spec.split(":")[0] == "ollama" else {}
    return init_backend(spec, **options)


def build_agent(backend=None):
    scout = Agent(
        name="Scout",
        maki_instance=backend or make_backend(),
        role="research assistant",
        instructions="Use the web_search tools to gather items, then answer briefly.",
    )
    scout.load_plugin("web_search")
    return scout


def main(topic="python", backend=None):
    scout = build_agent(backend)
    answer = scout.execute_task(
        f"Find 3 recent Hacker News stories about {topic}. List the title and url of each.",
        use_plugins=True,   # without this the agent never sees the tool
    )
    print(answer)
    return answer


if __name__ == "__main__":
    main(sys.argv[1] if len(sys.argv) > 1 else "python")

You should see three stories from Hacker News, each with a title and a link. The stories are real and recent, so they differ from day to day.

What changed from chapter 2

scout.load_plugin("web_search") gives the agent a set of methods it may call. The web_search plugin reads RSS feeds and news sites such as Hacker News. It needs no API key. It is not a general web search: it only looks at sources like these. The method Scout uses here, search_hackernews, searches Hacker News stories from the current week.

use_plugins=True tells the agent to offer those methods to the model for this task. Without it the agent never sees the tool and answers from memory, which is the most common reason a first tool-using agent seems to ignore its tool.

The model decides whether to call the tool. You never write the call yourself. The model reads the task, sees that it needs recent stories, asks Maki to run search_hackernews with a query, and Maki runs it and hands the results back. The model then writes the answer from those results.

The tool is limited on purpose

Plugins can have methods that change things: writing files, deleting, uploading. Maki keeps those off unless you pass allow_dangerous_tools=True when you create the agent. The web_search plugin only reads, so you do not need it. Keep it off unless you have a reason. A web page that an agent reads can contain text that tries to give the agent orders, and this default stops such text from making the agent write to your disk.

How well it works depends on the model

Calling a tool takes a model that can follow the format. In the author's tests the 4B model called the tool correctly every time, but a smaller or older model might not. If your agent writes about stories without calling the tool, the model is the usual cause. Try a larger one before you suspect your code.

Next, in chapter 4, you combine a tool, plain code and two agents into one pipeline.