Skip to content

Add a custom tool

Extend a running stack with your own tool: add it to the project's Python package, wire it into a room, exercise it, and cover it with a test. This continues from First steps.

Prerequisites

  • A generated stack, brought up (see First steps).
  • git and uv.
  • A stack generated by soliplex-template v0.16 or later. Earlier stacks put the package directly at src/<package>/ with its tests at the stack root, so none of the paths below will match; convert one with Migrating to the src/ project layout.

1. Add a tool

Your project is an installable Python package living in its own project directory under src/; tools live in src/<package>/src/<package>/tools.py (the greeting tool from First steps is there). Add a sibling:

# src/<package>/src/<package>/tools.py
def farewell(name: str) -> str:
    """Return a friendly farewell for ``name``."""
    return f"Goodbye, {name}! Come back soon."

The docstring matters: the LLM uses it as the tool's description.

2. Wire it into the custom room

Add the new tool to backend/environment/rooms/custom/room_config.yaml:

tools:
  - tool_name: "<package>.tools.greeting"
  - tool_name: "<package>.tools.farewell"

The backend watches both its config (--reload=config) and your package (a --reload-dirs flag on its serve command), so saving either file restarts it, picking up farewell. (If it doesn't appear, docker compose restart backend. A stack generated before the backend watched its package needs uv run <skill>/scripts/src_projects.py add <package> once to start.)

3. Test it in the room

Open the Custom Tool Demo room and ask:

Please say farewell to Ada.

The agent calls your new tool and replies:

Goodbye, Ada! Come back soon.

4. Add a unit test

The generated project is also a normal Python package with a test suite already wired up: src/<package>/tests/unit/test_tools.py covers greeting. Add a case for farewell beside it (tools is already imported at the top of that file):

# src/<package>/tests/unit/test_tools.py
def test_farewell_includes_name():
    result = tools.farewell("Ada")

    assert "Ada" in result

Create the dev environment once, then run the suite:

cd src/<package>
uv sync          # installs the dev dependencies, incl. pytest
uv run pytest

No setup is needed: the project's own pyproject.toml already declares pytest, puts its src/ on the path, and points testpaths at tests/unit/, so your new test is collected and passes. (Run these from the project directory, not the stack root — the root pyproject.toml is for stack tooling.)

That run uses the project's own lock file, not the versions the backend has. To run the same tests against what the backend actually imports your code with, run them in the dev-mode soliplex-dev service, which carries your project's dev dependencies on top of the backend's image:

docker compose run --rm --no-deps -w /app/src/<package> soliplex-dev \
    /app/.venv/bin/python -m pytest

(The first run builds soliplex-dev.) See Running its tests there.

5. Commit it

From the stack root again:

cd ../..            # back to the stack root from src/<package>
git add src/<package>/src/<package>/tools.py \
    src/<package>/tests/unit/test_tools.py \
    backend/environment/rooms/custom/room_config.yaml
git commit -m "Add a farewell tool (with a test) to the custom room"

Where next

That completes the core series: Next steps collects the optional walkthroughs you can take from here.

Related: Work with Gitea ships a change like the one you just made through a pull request hosted by the stack's own Gitea.