I’m going through a 16-week Agentic AI syllabus right now, and Week 1 is “Python for Agentic Systems” — OOP, typing, decorators. Instead of just reading about it, I built a small CLI toolkit that mimics how real agent frameworks register and run tools.
This post is about one piece of it: how a global tool registry works using __init_subclass__, and why agent frameworks need this pattern at all.
Repo’s at the bottom. Code below is real, from the actual project — not pseudocode.
The problem
Agent frameworks (LangGraph, CrewAI, PydanticAI) all need the same thing: a way for an LLM or planner to discover “what tools exist” without you manually maintaining a list somewhere.
The naive way is a dict you update by hand:
TOOLS = {
"search": SearchTool,
"summarize": SummarizeTool,
}
Enter fullscreen mode Exit fullscreen mode
This works until you forget to add an entry. Then your planner silently can’t find a tool that exists in your codebase.
The fix: self-registering classes
Here’s the actual registry from my project:
class ToolRegistry:
__slots__ = ()
_tools: dict[str, type[Any]] = {}
@classmethod
def register(cls, name: str, tool_cls: type[Any]) -> None:
existing = cls._tools.get(name)
if existing is not None and existing is not tool_cls:
raise DuplicateToolError(
f"tool name {name!r} is already registered by {existing.__name__}"
)
cls._tools[name] = tool_cls
Enter fullscreen mode Exit fullscreen mode
And the part that actually calls register() — __init_subclass__ on the base class:
class BaseTool(LoggingMixin, RetryMixin, MetricsMixin, ABC):
def __init_subclass__(
cls,
*,
tool_name: str | None = None,
description: str = "",
streamable: bool = False,
abstract: bool = False,
**kwargs: Any,
) -> None:
super().__init_subclass__(**kwargs)
if abstract:
return
if tool_name is None:
raise TypeError(f"{cls.__name__} must define tool_name='...'")
cls._tool_name = tool_name.strip().lower()
cls.description = description.strip()
cls._streamable = streamable
ToolRegistry.register(cls._tool_name, cls)
Enter fullscreen mode Exit fullscreen mode
__init_subclass__ fires automatically the moment Python defines a subclass — before you ever instantiate it. So a tool just declares itself:
class SearchTool(
BaseTool,
tool_name="search",
description="Searches a small in-memory knowledge base.",
streamable=True,
):
def execute(self, context: ToolContext) -> str:
...
Enter fullscreen mode Exit fullscreen mode
The moment this class body is parsed, SearchTool is in the registry. No manual list. No import-time side-effect hacks. Forget tool_name= and you get a TypeError immediately — not a silent miss three files away.
Why this matters for agent frameworks specifically
Once tools self-register, a CLI (or a planner LLM) can just ask “what do you have”:
def _list_tools() -> None:
for name, tool_cls in ToolRegistry.items():
tool = tool_cls()
print(f"{name:<12} {tool.metadata['description']}")
Enter fullscreen mode Exit fullscreen mode
$ python main.py list-tools
search Searches a small in-memory knowledge base and returns ranked notes.
summarize Creates a compact extractive summary of user-provided text.
translate Translates common demo phrases to Spanish or Urdu using a local lexicon.
Enter fullscreen mode Exit fullscreen mode
This is structurally the same problem LangGraph and CrewAI solve with their own tool-discovery mechanisms. Different implementation, same underlying need: a single source of truth that updates itself.
What’s also in the project
This registry is one piece. The same codebase has:
-
Descriptors (
ValidatedField,IdentifierField,IntegerRange) validatingToolConfigat assignment time -
Mixins + MRO —
BaseTool(LoggingMixin, RetryMixin, MetricsMixin, ABC)composes logging, retries, and metrics without inheritance spaghetti -
ParamSpec-based decorators (@log_execution,@measure_time) that wrap methods without breaking their signatures for mypy -
Generator-based streaming —
stream()yields tokens instead of faking it with string slicing
I’ll cover each of these in upcoming posts as I move through the syllabus.
Try it
git clone https://github.com/Sajid0875/agentic-systems-bootcamp
cd agent-ready-cli-toolkit
python main.py list-tools
python main.py describe search
python main.py run summarize "Agent frameworks register tools and stream results." --stream
Enter fullscreen mode Exit fullscreen mode
If you’re also learning agentic systems and want to compare notes on how different frameworks (CrewAI, PydanticAI, LangGraph) handle tool registration internally, drop it in the comments — genuinely curious how close/far off this mental model is from the real implementations.
답글 남기기