惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
阮一峰的网络日志
阮一峰的网络日志
T
Tailwind CSS Blog
博客园 - 【当耐特】
量子位
博客园 - 叶小钗
有赞技术团队
有赞技术团队
Jina AI
Jina AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - Franky
博客园 - 司徒正美
爱范儿
爱范儿
美团技术团队
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
V2EX
罗磊的独立博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Last Week in AI
Last Week in AI
Hugging Face - Blog
Hugging Face - Blog
I
InfoQ
D
DataBreaches.Net
宝玉的分享
宝玉的分享

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
I built a mini agent-tool framework to actually understan...
Sajid Islam · 2026-06-21 · via DEV Community

Sajid Islam

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,
}

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

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)

__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:
        ...

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']}")

$ 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.

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) validating ToolConfig at assignment time
  • Mixins + MROBaseTool(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 streamingstream() 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

Repo: https://github.com/Sajid0875/agentic-systems-bootcamp/tree/main/Week%201%20/Session%201/project_agent_cli%20

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.