Tool Registry Architecture in DeepSeek-Reasonix: How Built-in Tools Self-Register
Reasonix implements a two-phase registration system where built-in tools self-register at compile-time via init() functions into a global map, then copy into a per-session Registry at runtime, enabling deterministic tool discovery while supporting dynamic plugin injection.
The DeepSeek-Reasonix project separates tool discovery from execution through a sophisticated registry architecture. This design ensures that built-in tools located in internal/tool/builtin/ are automatically available at startup while maintaining flexibility for MCP servers and runtime plugins. Understanding the interaction between the global RegisterBuiltin mechanism and the per-run Registry struct is essential for developers extending the system's capabilities.
Global Compile-Time Registration of Built-in Tools
At package initialization, each built-in tool calls tool.RegisterBuiltin within its own init() function. This pattern appears consistently across files like internal/tool/builtin/writefile.go and internal/tool/builtin/webfetch.go.
The registration stores the tool in a package-level builtins map defined in internal/tool/tool.go, keyed by the tool's Name() method. The system panics on duplicate names, guaranteeing a one-to-one mapping:
// In internal/tool/tool.go
var builtins = map[string]Tool{}
func RegisterBuiltin(t Tool) {
name := t.Name()
if _, dup := builtins[name]; dup {
panic("tool: duplicate built-in " + name)
}
builtins[name] = t
}
Each built-in follows this registration pattern in its init() function:
// In internal/tool/builtin/writefile.go
func init() { tool.RegisterBuiltin(writeFile{}) }
Because these init functions compile into the binary, the global map is fully populated before any user code runs, creating a deterministic, immutable catalog of built-in capabilities.
The Per-Run Registry Structure and Methods
When a Reasonix session starts, the system creates a fresh Registry instance via tool.NewRegistry(). This per-run container holds the active set of tools and manages their lifecycle independently of the global built-in set.
The Registry struct maintains four key fields:
tools: Amap[string]Toolstoring active tool instancesorder: A[]stringpreserving insertion order for stable iterationcanon: Amap[string]json.RawMessagecaching canonical JSON schemassuspended: Amap[string]booltracking temporarily disabled prefixes
The NewRegistry() constructor initializes these maps:
func NewRegistry() *Registry {
return &Registry{
tools: map[string]Tool{},
canon: map[string]json.RawMessage{},
suspended: map[string]bool{},
}
}
The registry provides thread-safe methods for dynamic management. The Add(t Tool) method inserts or replaces tools while preserving first-seen order and respecting suspended prefixes:
func (r *Registry) Add(t Tool) {
r.mu.Lock()
defer r.mu.Unlock()
name := t.Name()
for prefix := range r.suspended {
if strings.HasPrefix(name, prefix) {
return
}
}
if _, ok := r.tools[name]; !ok {
r.order = append(r.order, name)
}
r.tools[name] = t
r.canon[name] = provider.CanonicalizeSchema(t.Schema())
}
Additional methods include RemovePrefix(prefix) for disconnecting entire MCP servers, SuspendPrefix/ResumePrefix for temporary disabling, and Get(name) for tool lookup.
Bootstrapping: Wiring Built-ins into the Session Registry
The connection between compile-time globals and runtime instances occurs during agent startup. The system retrieves all globally registered built-ins via tool.Builtins() and populates the fresh registry:
reg := tool.NewRegistry()
for _, b := range tool.Builtins() {
reg.Add(b)
}
This bootstrapping ensures every session begins with a complete, ordered list of built-in tools. After initialization, plugins and MCP connections can dynamically extend the registry by calling reg.Add() with new tool instances.
MCP Tools Namespacing and Dynamic Management
External MCP servers expose tools under the "mcp__" prefix (e.g., mcp__myserver__list_files). The registry supports namespace-aware operations through the SplitMCPName function:
func SplitMCPName(name string) (server, tool string, ok bool) {
if !strings.HasPrefix(name, MCPNamePrefix) {
return "", "", false
}
rest := name[len(MCPNamePrefix):]
parts := strings.SplitN(rest, "__", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", "", false
}
return parts[0], parts[1], true
}
When an MCP server disconnects, RemovePrefix("mcp__myserver__") removes all tools matching that namespace. The order slice ensures that iteration remains stable regardless of registration timing or removal operations.
Summary
- Compile-time registration: Built-in tools call
RegisterBuiltinininit()functions, populating a global map ininternal/tool/tool.gobefore runtime. - Runtime isolation: Each session creates a fresh
RegistryviaNewRegistry(), holding active tools in a thread-safe map with ordered iteration. - Bootstrapping flow: The system copies all entries from
tool.Builtins()into the registry at startup, then accepts dynamic additions viareg.Add(). - Namespace management: MCP tools use
"mcp__"prefixes, enabling bulk removal viaRemovePrefix()and temporary suspension viaSuspendPrefix().
Frequently Asked Questions
How do I register a new built-in tool in Reasonix?
Create a new file in internal/tool/builtin/ implementing the Tool interface with Name(), Description(), Schema(), ReadOnly(), and Execute() methods. Add an init() function that calls tool.RegisterBuiltin() with your tool instance. The package-level init ensures registration occurs at compile-time.
What happens if two built-in tools have the same name?
The RegisterBuiltin function in internal/tool/tool.go checks for duplicates using the builtins map. If a name collision occurs, the function panics with "tool: duplicate built-in " + name, preventing the binary from starting and ensuring unique tool identifiers.
How does Reasonix handle tools from external MCP servers?
MCP servers register tools with the "mcp__" prefix (e.g., mcp__servername__toolname). The registry treats these as standard tools via Add(), but provides namespace-aware methods like RemovePrefix() to disconnect entire servers and SplitMCPName() to parse server-tool relationships.
Can I temporarily disable tools without removing them from the registry?
Yes. The Registry maintains a suspended map that tracks disabled prefixes. Calling SuspendPrefix(prefix) blocks all tools with names starting with that prefix from being added or executed, while ResumePrefix(prefix) restores them without requiring re-registration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →