Lessons 01 through 10 built a tool definition, a JSON Schema, a request/response lifecycle, a first working loop, description quality, granularity, output shaping, structured error handling, and concurrency grouping — each as its own standalone piece of logic. A real agent does not call ten separate helper modules to make one tool call; it calls a single component that already knows how to register a tool, validate a call against it, decide which tools to expose for the task at hand, and dispatch by name. This lesson builds that component: a ToolRegistry.
The registry's central value is not convenience, it is correctness under change. A tool definition handed to the model is only useful if it accurately describes what the underlying function actually does and actually accepts — and the moment a definition is maintained as a separate hand-written dict, nothing stops it from silently disagreeing with the function once someone renames a parameter or adds a new one. A definition generated from the function itself — its name, its docstring, its type hints — cannot disagree with the function, because it is not a second artifact at all.
By the end of this lesson the registry will do six things: derive a tool's name, description, and input_schema directly from a plain Python function; validate incoming arguments against that derived schema before dispatch, returning structured errors instead of raising; export the tool list in the shape the API expects; scope which tools are exported for a given task, since every exposed definition costs tokens whether or not it is ever called; dispatch by name with unknown-tool handling and lesson 10's concurrency grouping; and carry per-tool metadata — mutates, permission, idempotent — that drives real authorization and retry decisions rather than sitting there as documentation.