The Command Center: Dynamic Tool Registries & Automatic Schema Synthesis
If an LLM has no way to interact with the external world, it is just a calculator that predicts words. To make it useful, we must give it tools: APIs, databases...
If an LLM has no way to interact with the external world, it is just a calculator that predicts words.
To make it useful, we must give it tools: APIs, databases, filesystems, and custom scripts.
But there is a major engineering hurdle at the boundary between a probabilistic language model and a deterministic operating system: type safety and schema compliance.
LLMs do not call Python functions directly. They produce JSON payloads containing a tool name and its arguments. It is the runtime’s job to advertise the available tools to the model in a strict format, usually JSON Schema or OpenAPI.
The runtime must then receive the model’s call parameters, validate them, parse them, execute the actual Python function safely, catch errors, and package the result.
Doing this manually for dozens of tools quickly becomes an engineering nightmare of boilerplate.
In this article, we will explore how SolidState solves this cleanly using three classic software design patterns:
The Registry Pattern
Metaprogramming and Reflection
The Command Pattern
The Registry Pattern: Decentralizing Capabilities
Instead of hardcoding tool execution in a massive nested if/else block, we use the Registry Pattern.
The registry acts as a centralized catalog of tools. It decouples tool registration from tool execution.
A registry maps a unique string identifier, the tool name, to a metadata object containing the executable function, parameter definitions, safety policies, and risk levels.
The registry exposes a clean, single-method interface for developers to register new capabilities.
By decoupling registration, we can dynamically load, disable, or audit tools at runtime without altering the core state machine logic.
The Metaprogramming Pattern: Automatic Schema Synthesis
When registration occurs, how do we get the JSON Schema that the LLM needs?
Writing schemas by hand is prone to errors. More importantly, the schema can drift away from the actual Python parameters.
To solve this, we use metaprogramming and reflection.
Reflection is the ability of a program to inspect its own structure at runtime. Python makes this straightforward through the inspect module and type hints.
Instead of writing a JSON schema manually, the runtime inspects the Python function.
The function name is inferred directly from fn.__name__.
The description is extracted from the function’s docstring using fn.__doc__.
The parameters are parsed using inspect.signature, which allows the runtime to determine parameter types such as str, int, and bool, and also identify which arguments are required based on whether they have default values.
The Magic of Pydantic Integration
Sometimes, a tool requires complex nested configurations.
For example, creating or evaluating a user profile may involve several related fields. Rather than exposing many individual arguments, we can accept a single Pydantic model parameter.
Our schema generator can reflect Pydantic classes instantly.
Because the runtime handles the reflection, the code becomes the schema.
There is zero risk of the documentation drifting from the actual implementation.
The Command Pattern: Decoupling Execution from Definition
When the model decides to invoke a tool, it outputs a payload representing a command.
For example:
“Run tool evaluate_risk with arguments { 'profile': { 'score': 680, ... } }.”
In SolidState, we model this as a ToolCall and execute it through the Command Pattern.
The executor is responsible for resolving the call, unpacking variables, parsing JSON, casting Pydantic classes, and handling failures cleanly.
High-Level Execution Workflow
Instead of letting tools execute unsafely, the executor runs them in a controlled sandbox.
First, there is autoboxing.
If a parameter expects a Pydantic model and the LLM sends a raw JSON dictionary, the executor automatically casts that dictionary into the rich Pydantic object.
For example:
Second, there is async integration.
The runtime detects whether the function is synchronous or asynchronous and routes it to the correct execution path, such as asyncio.gather or asyncio.to_thread.
Third, there is error isolation.
Any exception thrown inside the tool, such as a database timeout or API failure, is caught, wrapped in a ToolResult marked as "error", and returned to the message stream.
Self-Healing and Error Isolation
Notice how errors are trapped within the executor.
If a database connection timeout occurs inside a tool, it does not crash the agent server or the runtime thread. Instead, it returns a descriptive error result.
That error is appended to the message history.
This allows the LLM to read the trace, reason about the failure, and try an alternative approach. In some cases, it can self-heal by executing a retry.
Summary: Designing for Developers
By combining the Registry Pattern for cataloging capabilities, Metaprogramming and Reflection for zero-boilerplate schema generation, and the Command Pattern for isolated execution, we create a tool system that is easy to extend, robust, and type-safe.
It is easy to extend because developers only need to write a Python function with docstrings and type hints.
It is robust because errors are caught, logged, and isolated.
It is type-safe because raw JSON data can be automatically converted into typed Pydantic models.
In the next chapter, “The Guardian at the Gate: Policy Evaluation, Intercepts, and Human-in-the-Loop Orchestration”, we will explore what happens before a tool is executed: how to intercept calls, enforce safety constraints, and pause execution for human approval.