How to Write a Tool Plugin¶
Goal¶
Ship a Stargraph tool as an installable Python distribution that Stargraph
discovers via importlib.metadata entry points and registers through
pluggy.
Prerequisites¶
- Stargraph installed (
pip install stargraph>=0.2). - Python 3.13+,
hatchlingor your packaging backend of choice. - Read the Plugin Model — especially the two-stage discovery contract.
Steps¶
1. Lay out the package¶
_plugin.py will hold both the stargraph_plugin() manifest factory and
the @tool-decorated callable.
Verify: find . -name pyproject.toml -o -name '*.py' shows the four
files above.
2. Implement the stargraph_plugin() manifest factory¶
Stargraph's loader (see stargraph.plugin._manifest) imports this
factory before any tool code, then validates the returned
PluginManifest against
STARGRAPH_API_VERSION_MAJOR=1 and a namespace conflict map.
# src/my_tool_plugin/_plugin.py
from stargraph.ir import PluginManifest
def stargraph_plugin() -> PluginManifest:
return PluginManifest(
name="my-tool-plugin",
version="0.1.0",
api_version="1",
namespaces=["mypkg"],
provides=["tool"],
order=5000,
)
namespaces must match the namespace you pass to @tool below;
order controls registration priority (0..10000, default 5000,
collisions are fatal).
Verify: python -c "from my_tool_plugin._plugin import stargraph_plugin;
print(stargraph_plugin())" prints a populated PluginManifest.
3. Decorate the tool callable¶
# src/my_tool_plugin/_plugin.py (continued)
from decimal import Decimal
from stargraph.ir import ToolSpec
from stargraph.tools import ReplayPolicy, SideEffects, tool
@tool(
name="echo",
namespace="mypkg",
version="0.1.0",
side_effects=SideEffects.none,
description="Return the input string verbatim.",
)
def echo(message: str) -> dict[str, str]:
"""Echo a string back to the caller."""
return {"echoed": message}
def register_tool() -> list[ToolSpec]:
"""`stargraph.tools` entry-point factory — yields ToolSpec records."""
return [echo.spec]
The @tool decorator (see stargraph.tools.decorator):
- attaches a
ToolSpecto the callable asecho.spec, - derives input/output JSON Schemas from the type annotations via
pydantic.TypeAdapterwhen you omitinput_schema=/output_schema=, - defaults
replay_policyfromside_effectsper FR-21 (none|read → recorded_result,write|external → must_stub), - normalises
requires_capability=intoToolSpec.permissions.
Verify: python -c "from my_tool_plugin._plugin import echo;
print(echo.spec.model_dump())" prints a populated spec.
4. Wire entry points¶
# pyproject.toml
[project]
name = "my-tool-plugin"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = ["stargraph>=0.2"]
[project.entry-points."stargraph"]
stargraph_plugin = "my_tool_plugin._plugin:stargraph_plugin"
[project.entry-points."stargraph.tools"]
echo = "my_tool_plugin._plugin:register_tool"
The stargraph group binds the manifest factory; the stargraph.tools group
binds the tool factory. Both are required — Stargraph refuses to register a
plugin distribution that contributes a stargraph.tools entry but no
stargraph_plugin factory (PluginLoadError).
5. Test the tool¶
# tests/test_echo.py
from my_tool_plugin._plugin import echo
def test_echo_passthrough():
assert echo(message="hello") == {"echoed": "hello"}
assert echo.spec.namespace == "mypkg"
assert echo.spec.side_effects == "none"
Verify: pytest -q is green.
Wire it up¶
Install the distribution into the environment running Stargraph:
pip install -e ./my-tool-plugin
STARGRAPH_TRACE_PLUGINS=1 python -c "from stargraph.plugin.loader import build_plugin_manager; build_plugin_manager()"
You should see structured plugin.discovery.entry,
plugin.manifest.validated, and plugin.register events for
my-tool-plugin.
Verify¶
stargraph inspect <run_id>(after a run that usesmypkg.echo) shows the tool call in the timeline.- Importing without
STARGRAPH_TRACE_PLUGINSstill works and stays silent.
Troubleshooting¶
Common failure modes
PluginLoadError: ... no stargraph_plugin manifest factory— the dist registered astargraph.toolsentry but forgot the[project.entry-points."stargraph"] stargraph_plugin = ...line.PluginLoadError: api_version '2' incompatible with Stargraph major 1— bump Stargraph or pin the manifest'sapi_versionback to"1".PluginLoadError: namespace conflict— two installed distributions claimed the samenamespaces[]entry. Uninstall the offender named in the error.PluginLoadError: plugin order collision— pick a uniqueorderinteger in[0, 10000].
See also¶
- Plugin Manifest reference
- Tools reference
- Hookspecs
- Build an agent — using your tool from a Skill.