Tool Catalog: Dynamic Tool Loading/Unloading
Tool Catalog: Dynamic Tool Loading/Unloading
When agents need 100+ tools, sending all tool definitions to the LLM wastes tokens and degrades performance. The Tool Catalog lets agents dynamically discover and load only the tool categories they need.
Quick Start
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: [readFile, writeFile], // always-on tools (outside catalog)
identities: [{ connector: 'github' }], // connector scope
context: {
model: 'gpt-4.1',
features: { toolCatalog: true },
toolCategories: ['web', 'code'], // built-in category scope
plugins: {
toolCatalog: {
pinned: ['web'], // always loaded, LLM can't unload
},
},
},
});
The agent gets 3 metatools: tool_catalog_search, tool_catalog_load, tool_catalog_unload. It can browse available categories, load what it needs, and unload when done.
Important: Plugin tools (memory, context, instructions, etc.) are always available and completely separate from the catalog. They cannot be unloaded.
How Scoping Works
The Tool Catalog has two independent scoping mechanisms:
| Scope | Controls | Config |
|---|---|---|
toolCategories |
Built-in categories (filesystem, web, code, shell, etc.) | context.toolCategories |
identities |
Connector categories (connector:github, connector:slack, etc.) | Agent.create({ identities }) |
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
// Controls which connector categories appear in catalog
identities: [
{ connector: 'github' },
{ connector: 'slack' },
{ connector: 'microsoft', accountId: 'work' },
],
context: {
model: 'gpt-4.1',
features: { toolCatalog: true },
// Controls which built-in categories appear in catalog
toolCategories: ['filesystem', 'web', 'code'],
},
});
// LLM sees: filesystem, web, code, connector:github, connector:slack, connector:microsoft:work
Scoping syntax for toolCategories:
// Allowlist (string[] shorthand)
toolCategories: ['web', 'knowledge']
// Explicit allowlist
toolCategories: { include: ['web', 'knowledge'] }
// Blocklist (all except these)
toolCategories: { exclude: ['desktop', 'shell'] }
// No scope = all built-in categories visible
Pinned Categories
Pinned categories are always loaded and the LLM cannot unload them. Use this for tools the agent must always have access to:
const contextConfig = {
features: { toolCatalog: true },
toolCategories: ['filesystem', 'web', 'code'],
plugins: {
toolCatalog: {
pinned: ['filesystem'], // always loaded, can't unload
},
},
};
Pinned vs always-on tools (tools array):
tools: [myTool]onAgent.create()— individual tools always available, outside the catalog entirelypinned: ['filesystem']— entire category loaded via catalog, visible in catalog listings as[PINNED], but cannot be unloaded
Behavior:
- Pinned categories are loaded automatically when the plugin initializes
tool_catalog_unloadreturns an error for pinned categories- Pinned categories don't count toward
maxLoadedCategorieslimit - During compaction, pinned categories are never evicted
Auto-Loading Categories
Pre-load categories so the agent doesn't need an extra turn. Unlike pinned, auto-loaded categories can be unloaded by the LLM:
const pluginConfigs = {
plugins: {
toolCatalog: {
pinned: ['filesystem'], // always loaded
autoLoadCategories: ['web', 'knowledge'], // pre-loaded, can be unloaded
maxLoadedCategories: 10, // limit (excludes pinned)
},
},
};
Dynamic Instructions
The LLM receives dynamic instructions that list exactly which categories are available, with markers:
## Tool Catalog
Your core tools (memory, context, instructions, etc.) are always available.
Additional tool categories can be loaded on demand from the catalog below.
**Available categories:**
- filesystem (6 tools) [PINNED]: Read, write, edit, search, and list files
- web (1 tool): Fetch and process web content
- code (1 tool): Execute JavaScript code in sandboxed VM
- connector:github (9 tools): Authenticated API + 8 GitHub tools
- connector:slack (11 tools): Authenticated API + 10 Slack tools
**Best practices:**
- Search first to find the right category before loading.
- Unload categories you no longer need to keep context lean.
- Categories marked [PINNED] are always available and cannot be unloaded.
ToolCatalogRegistry
Static global registry for tool categories (like Connector and StorageRegistry). Register your custom categories at app startup:
import { ToolCatalogRegistry } from 'oneringai';
ToolCatalogRegistry.registerCategory({
name: 'knowledge',
displayName: 'Knowledge Graph',
description: 'Search entities, get facts, manage references',
});
ToolCatalogRegistry.registerTools('knowledge', [
{ name: 'entity_search', displayName: 'Entity Search', description: 'Search people/orgs', tool: entitySearch, safeByDefault: true },
]);
Built-in tools from registry.generated.ts are auto-registered on first access.
Resolving Tool Names
For executors that resolve string[] tool names to ToolFunction[]:
const tools = ToolCatalogRegistry.resolveTools(
['entity_search', 'github_api'],
{ includeConnectors: true }
);
Tools
| Tool | Description | Permission |
|---|---|---|
tool_catalog_search |
Browse categories, list tools, keyword search | Always allowed |
tool_catalog_load |
Load a category's tools into the agent | Always allowed |
tool_catalog_unload |
Unload a category to free token budget | Always allowed |
Full Configuration Reference
interface ToolCatalogPluginConfig {
/** Scope for built-in categories (does NOT affect connector categories) */
categoryScope?: string[] | { include: string[] } | { exclude: string[] };
/** Categories pre-loaded on init (can be unloaded by LLM) */
autoLoadCategories?: string[];
/** Categories always loaded (cannot be unloaded by LLM) */
pinned?: string[];
/** Max loaded categories at once, excluding pinned (default: 10) */
maxLoadedCategories?: number;
/** Auth identities for connector filtering (usually set via Agent.create) */
identities?: AuthIdentity[];
}