Moltis
Architecture

Tool Registry

The tool registry manages all tools available to the agent during a conversation. It tracks where each tool comes from, which audience may use it, and supports filtering by both properties.

Tool Sources

Every registered tool has a ToolSource that identifies its origin:

  • Builtin — tools shipped with the binary (exec, web_fetch, etc.)
  • Mcp { server } — tools provided by an MCP server, tagged with the server name
  • Wasm { component_hash } — tools provided by a WASM component

This replaces the previous convention of identifying MCP tools by their mcp__ name prefix, providing type-safe filtering instead of string matching.

Registration

Every entry also has a ToolAudience:

  • Trusted — authenticated owner and authorized operator turns
  • Public — eligible for explicitly configured untrusted origins such as webhook payloads

Registration is fail-closed. Built-in tools are trusted-only by default, and all MCP and WASM tools are trusted-only. A reviewed built-in must explicitly use register_public to become public. Tool descriptions and third-party metadata cannot grant public access.

Public registration does not grant a tool to messaging-channel guests or shared rooms. Those turns receive an additional deny-all request policy. A webhook also receives no tools unless its configuration explicitly opts in.

// Trusted-only built-in tool (the default)
registry.register(Box::new(MyTool::new()));

// Built-in reviewed as safe for untrusted input
registry.register_public(Box::new(PublicTool::new()));

// Trusted-only MCP tool, tagged with server name
registry.register_mcp(Box::new(adapter), "github".into());

Duplicate names are rejected rather than overwritten. Intentional wrappers use replace, which preserves the original source and audience metadata.

Filtering

When MCP tools are disabled for a session, the registry can produce a filtered copy:

// Type-safe: filters by ToolSource::Mcp variant
let no_mcp = registry.clone_without_mcp();

// Remove all MCP tools in-place (used during sync)
let removed_count = registry.unregister_mcp();

// Keep only tools explicitly reviewed for public use
let public = registry.clone_for_audience(ToolAudience::Public);

Schema Output

list_schemas() includes source metadata in every tool schema:

{
  "name": "exec",
  "description": "Execute a command",
  "parameters": { ... },
  "source": "builtin",
  "audience": "trusted"
}
{
  "name": "mcp__github__search",
  "description": "Search GitHub",
  "parameters": { ... },
  "source": "mcp",
  "mcpServer": "github",
  "audience": "trusted"
}

The source and audience fields are available to the UI for diagnostics. The audience is enforced by registry filtering before model tool schemas are built; it is not an instruction that the model is expected to follow.

Lazy Registry Mode

By default every LLM turn includes full JSON schemas for all registered tools. With many MCP servers this can burn 15,000+ tokens per turn. Lazy mode replaces all tool schemas with a single tool_search meta-tool that the model uses to discover and activate tools on demand.

Configuration

[tools]
registry_mode = "lazy"   # default: "full"

How it works

  1. The model receives only tool_search in its tool list.
  2. tool_search(query="memory") returns name + description pairs (max 15), no schemas.
  3. tool_search(name="memory_search") returns the full schema and activates the tool.
  4. On the next iteration the model calls memory_search directly — standard pipeline, hooks fire normally.

The runner re-computes schemas each iteration, so activated tools appear immediately. The iteration limit is tripled in lazy mode to account for the extra discovery round-trips.

When to use

  • Many MCP servers connected (50+ tools)
  • Long conversations where input token cost matters
  • Sub-agent runs that only need a few specific tools

In full mode (default), all schemas are sent every turn — no behavioral change from before this feature.