Appam
API Reference

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() -> Self

Creates 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() -> Self

Creates 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) -> usize

Returns the number of registered tools.

.is_empty()

pub fn is_empty(&self) -> bool

Returns 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(&registry));
let agent2 = RuntimeAgent::new("agent-2", "...", Arc::clone(&registry));
// Both agents share the same tools

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