ToolRegistry
Registry for managing and executing tools by name.
ToolRegistry is a thread-safe, dynamic registry that maps tool names to their implementations. It is used by both RuntimeAgent and TomlAgent to resolve and execute tool calls from the LLM.
Construction
ToolRegistry::new()
pub fn new() -> SelfCreates a new empty tool registry.
use appam::tools::ToolRegistry;
let registry = ToolRegistry::new();ToolRegistry also implements Default:
let registry = ToolRegistry::default();ToolRegistry::with_builtins()
pub fn with_builtins() -> SelfCreates a registry pre-populated with built-in tools. Currently returns an empty registry (built-in tools are registered separately).
Registration
.register()
pub fn register(&self, tool: Arc<dyn Tool>)Registers a tool in the registry. If a tool with the same name already exists, it is silently replaced (last write wins).
use std::sync::Arc;
let registry = ToolRegistry::new();
registry.register(Arc::new(MyTool));.register_many()
pub fn register_many(&self, tools: Vec<Arc<dyn Tool>>)Registers multiple tools at once. Equivalent to calling .register() for each tool.
registry.register_many(vec![
Arc::new(Tool1),
Arc::new(Tool2),
Arc::new(Tool3),
]);Resolution
.resolve()
pub fn resolve(&self, name: &str) -> Option<Arc<dyn Tool>>Looks up a tool by name. Returns a cloned Arc to the tool implementation, or None if the tool is not registered.
if let Some(tool) = registry.resolve("bash") {
let result = tool.execute(json!({ "command": "ls" }))?;
}.execute()
pub fn execute(&self, name: &str, args: serde_json::Value) -> Result<serde_json::Value>Convenience method that combines resolution and execution. Returns an error if the tool is not found or execution fails.
let result = registry.execute("read_file", json!({ "path": "/tmp/test.txt" }))?;This is equivalent to:
let tool = registry.resolve(name)
.ok_or_else(|| anyhow!("Tool not found: {}", name))?;
tool.execute(args)Inspection
.list()
pub fn list(&self) -> Vec<String>Returns a sorted vector of all registered tool names.
let names = registry.list();
// ["bash", "read_file", "write_file"].len()
pub fn len(&self) -> usizeReturns the number of registered tools.
.is_empty()
pub fn is_empty(&self) -> boolReturns true if no tools are registered.
Removal
.unregister()
pub fn unregister(&self, name: &str) -> Option<Arc<dyn Tool>>Removes a tool by name. Returns the removed tool if it existed, or None if not found.
if let Some(removed) = registry.unregister("deprecated_tool") {
println!("Removed: {}", removed.name());
}.clear()
pub fn clear(&self)Removes all tools from the registry.
Thread Safety
ToolRegistry uses Arc<RwLock<HashMap>> internally:
- Reads (
resolve,list,len,is_empty) acquire a read lock and can proceed concurrently. - Writes (
register,unregister,clear) acquire a write lock and are serialized.
The registry implements Clone (cloning the inner Arc), so multiple agents can share the same registry.
let registry = Arc::new(ToolRegistry::new());
registry.register(Arc::new(shared_tool));
let agent1 = RuntimeAgent::new("agent-1", "...", Arc::clone(®istry));
let agent2 = RuntimeAgent::new("agent-2", "...", Arc::clone(®istry));
// Both agents share the same toolsName Collision Behavior
When registering a tool with a name that already exists, the new tool silently replaces the old one. This is intentional -- it allows extending TOML-configured agents by overriding specific tools:
let agent = TomlAgent::from_file("agent.toml")?;
// Override the "bash" tool with a sandboxed version
agent.registry().register(Arc::new(SandboxedBashTool));Source
Defined in src/tools/registry.rs.