Build a read-only Tool plugin

Exercise objective

Understand and run an independent text_stats Tool plugin. Test its Tool contract first, verify entry-point discovery second, and finish with one real DeepSeek Tool Call. Do not modify nanobot core at any point.

Prerequisites

  • Complete the Workspace Skill exercise.
  • Be able to create and use an isolated Python virtual environment.
  • Read ToolLoader and ToolRegistry.
  • Use the lab workspace and configuration.

Example location

This learning library includes a runnable example:

example/nanobot/learning-EN/examples/text-stats-tool-plugin/
├── pyproject.toml
├── nanobot_text_stats/
│   ├── __init__.py
│   └── tool.py
└── tests/
    └── test_tool.py

Source to read

Read the example first, then compare it with:

  • nanobot/agent/tools/base.py::Tool
  • nanobot/agent/tools/schema.py
  • nanobot/agent/tools/loader.py::ToolLoader
  • nanobot/agent/tools/registry.py::ToolRegistry
  • tests/agent/test_tool_loader_entrypoints.py

Predict before running

  • Must the entry-point key equal Tool.name?
  • What behavior does read_only=True affect?
  • If text is missing, does execute() return the error, or does Registry?
  • Which implementation wins if a plugin and a built-in Tool share a name?

Procedure

1. Read the Tool

TextStatsTool:

  • is named text_stats;
  • requires a text argument of 1–20000 characters;
  • returns a JSON string;
  • reads neither files nor the network;
  • sets read_only=True, so it may run concurrently in non-exclusive mode.

Explain each design choice in your own words before proceeding.

2. Install the source checkout

In a dedicated virtual environment:

Set-Location "example\nanobot\code-EN"
python -m pip install -e .

3. Install the plugin

python -m pip install -e "..\learning-EN\examples\text-stats-tool-plugin" --no-deps

The checkout from the previous step already provides nanobot. --no-deps prevents pip from installing a different nanobot release from PyPI.

4. Run the Tool unit tests

From example/nanobot/learning-EN/examples/text-stats-tool-plugin:

python -m pytest tests -q

5. Verify the entry point

python -c "from nanobot.agent.tools.loader import ToolLoader; print(sorted(ToolLoader()._discover_plugins()))"

The result should include:

text-stats

This is the entry-point key. The function name exposed to the model is text_stats.

6. Verify Registry behavior

In the debugger or a short-lived Python REPL:

  1. create a ToolContext;
  2. create a ToolRegistry;
  3. call ToolLoader().load(ctx, registry);
  4. check registry.has("text_stats");
  5. execute both valid and invalid arguments.

7. Real DeepSeek smoke test

Restart the nanobot process so that it discovers the entry point again. Send:

You must use the text_stats tool to analyze the following text. Tell me its characters, non_whitespace_characters, and lines:
first line
second line

Confirm the Tool Call name and arguments at a Runner breakpoint. Do not infer that the Tool ran merely from the final natural-language response.

Expected observations

  • ToolLoader discovers the independent package.
  • The Provider Tool schema includes text_stats.
  • Registry rejects missing or oversized arguments.
  • A valid result first returns to the model as a tool message.
  • The plugin requires no AgentLoop modification.
  • read_only makes the base class’s concurrency_safe property true.

Completion criteria

  • The example tests pass.
  • The entry point is discoverable.
  • Registry can execute the Tool.
  • A real model invokes the Tool at least once.
  • Explain the distinction among the entry-point key, class name, and Tool name.
  • example/nanobot/code-EN has no source diff.

Verification

Add one more test that checks one of these cases:

  • an empty string fails;
  • 20001 characters fail;
  • newline counting is correct.

The example area is explicitly writable for this exercise. Rerun the plugin tests and confirm that the diff is confined to the example directory.

Review questions

  1. Why does the example use a pure, read-only Tool?
  2. Does the framework prove that read_only=True is truthful?
  3. Why test through Registry before making a real request?
  4. If a future version adds a URL argument, which security controls become necessary?

Next: Capstone extension design review