Dynamic Tool Management
Dynamic Tool Management
Control tools at runtime for all agent types. Enable, disable, organize, and select tools dynamically.
Unified Tool Management Architecture
AgentContextNextGen is the single source of truth for ToolManager. All agents access tools through a single ToolManager instance owned by the context:
agent.tools === agent.context.tools- Same ToolManager instance- Tool changes via either API are immediately reflected in the other
- No duplicate tool storage or sync issues
Quick Start
import { Agent } from 'oneringai';
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: [weatherTool, emailTool, databaseTool],
});
// UNIFIED: agent.tools and agent.context.tools are the SAME instance
console.log(agent.tools === agent.context.tools); // true
// Disable tool temporarily
agent.tools.disable('database_tool');
// Changes via agent.context.tools are immediately reflected
agent.context.tools.enable('database_tool');
console.log(agent.tools.listEnabled().includes('database_tool')); // true
// Run without database access
agent.tools.disable('database_tool');
await agent.run('Check weather and email me');
// Re-enable later
agent.tools.enable('database_tool');
ToolManager API
Every agent has a tools property that returns the ToolManager owned by the context. Both agent.tools and agent.context.tools return the same instance:
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: [tool1, tool2],
});
// UNIFIED: Both access paths return the same ToolManager instance
console.log(agent.tools === agent.context.tools); // true
// Access ToolManager via either path
const toolManager = agent.tools;
// OR: const toolManager = agent.context.tools; // Same instance!
// Register new tool
toolManager.register(tool3, {
namespace: 'data',
priority: 10,
enabled: true,
});
// Unregister tool
toolManager.unregister('tool_name');
// Enable/disable
toolManager.enable('tool_name');
toolManager.disable('tool_name');
// Check if enabled
const isEnabled = toolManager.isEnabled('tool_name');
// List tools
const all = toolManager.list(); // All tools
const enabled = toolManager.listEnabled(); // Only enabled
Tool Options
interface ToolOptions {
/** Whether the tool is enabled. Default: true */
enabled?: boolean;
/** Namespace for grouping related tools. Default: 'default' */
namespace?: string;
/** Priority for selection ordering. Higher = preferred. Default: 0 */
priority?: number;
/** Conditions for context-aware enabling (all must pass) */
conditions?: ToolCondition[];
/** Tags for categorization and search */
tags?: string[];
/** Category grouping */
category?: string;
/** Source identifier (built-in, connector, custom, mcp, etc.) */
source?: string;
}
interface ToolCondition {
type: 'mode' | 'context' | 'custom';
predicate: (context: ToolSelectionContext) => boolean;
}
Namespaces
Organize tools by category:
// Register tools with namespaces
agent.tools.register(weatherTool, { namespace: 'external-api' });
agent.tools.register(emailTool, { namespace: 'communication' });
agent.tools.register(databaseReadTool, { namespace: 'database' });
agent.tools.register(databaseWriteTool, { namespace: 'database' });
// Disable all database tools
agent.tools.disableNamespace('database');
Priority
Control tool selection order:
agent.tools.register(primaryWeatherTool, {
priority: 100, // High priority
});
agent.tools.register(fallbackWeatherTool, {
priority: 10, // Low priority (fallback)
});
// selectForContext() returns tools in priority order; priority does not force model choice or retry
Conditions
Dynamic enabling based on context:
agent.tools.register(adminTool, {
conditions: [
{ type: 'custom', predicate: (context) => context.custom?.role === 'admin' },
],
});
// Tool only available when condition is met
const selected = agent.tools.selectForContext({
custom: { role: 'admin' },
});
Context-Aware Selection
interface ToolSelectionContext {
/** Current user input or task description */
input?: string;
/** Current agent mode (for UniversalAgent) */
mode?: string;
/** Current task name (for TaskAgent) */
currentTask?: string;
/** Recently used tools (to avoid repetition) */
recentTools?: string[];
/** Token budget for tool definitions */
tokenBudget?: number;
/** Custom context data */
custom?: Record<string, unknown>;
}
// Select tools based on context
const tools = agent.tools.selectForContext({
mode: 'executing',
custom: { environment: 'production', role: 'admin', permissions: ['write'] },
});
// Only tools matching this explicit selection context are returned
State Persistence
Save and restore tool configuration:
// Get current state
const state = agent.tools.getState();
// Save to file
await fs.writeFile('./tool-config.json', JSON.stringify(state));
// Later... load state
const savedState = JSON.parse(await fs.readFile('./tool-config.json', 'utf-8'));
agent.tools.loadState(savedState);
// Metadata restored for re-registered tools; executable functions are not serialized
Events
Listen to tool changes:
agent.tools.on('tool:registered', ({ name, namespace, enabled }) => {
console.log(`Tool registered: ${name} (namespace: ${namespace}, enabled: ${enabled})`);
});
agent.tools.on('tool:unregistered', ({ name }) => {
console.log(`Tool unregistered: ${name}`);
});
agent.tools.on('tool:enabled', ({ name }) => {
console.log(`Tool enabled: ${name}`);
});
agent.tools.on('tool:disabled', ({ name }) => {
console.log(`Tool disabled: ${name}`);
});
Advanced Patterns
Environment-Based Tools
const isDevelopment = process.env.NODE_ENV === 'development';
agent.tools.register(debugTool, {
enabled: isDevelopment,
namespace: 'debug',
});
agent.tools.register(productionTool, {
enabled: !isDevelopment,
namespace: 'production',
});
Permission-Based Tools
function createAgentWithPermissions(userRole: string) {
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
});
// Register all tools
agent.tools.register(readTool, {
namespace: 'data',
priority: 100,
});
agent.tools.register(writeTool, {
namespace: 'data',
priority: 90,
enabled: userRole === 'admin', // Only for admins
});
agent.tools.register(deleteTool, {
namespace: 'data',
priority: 80,
enabled: userRole === 'super-admin', // Only for super admins
});
return agent;
}
Rate-Limited Tools
class RateLimitedToolManager {
private calls = new Map<string, number>();
private limits = new Map<string, number>();
constructor(private agent: Agent) {}
registerWithLimit(tool: ToolFunction, limit: number) {
this.agent.tools.register(tool);
this.limits.set(tool.definition.function.name, limit);
this.calls.set(tool.definition.function.name, 0);
}
async execute(name: string, args: unknown) {
const count = this.calls.get(name) || 0;
const limit = this.limits.get(name);
if (limit && count >= limit) {
throw new Error(`Rate limit exceeded for ${name}`);
}
this.calls.set(name, count + 1);
return await this.agent.tools.execute(name, args); // Keep the shared permission/outcome pipeline
}
}
Dynamic Tool Loading
class PluginManager {
constructor(private agent: Agent) {}
async loadPlugin(pluginPath: string) {
const plugin = await import(pluginPath);
for (const tool of plugin.tools) {
this.agent.tools.register(tool, {
namespace: plugin.name,
category: plugin.name,
});
}
console.log(`Loaded plugin: ${plugin.name}`);
}
unloadPlugin(pluginName: string) {
for (const name of this.agent.tools.getByCategory(pluginName)) {
this.agent.tools.unregister(name);
}
console.log(`Unloaded plugin: ${pluginName}`);
}
}
Backward Compatibility
The old API still works:
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: [tool1, tool2], // Still works!
});
// Old methods still work
agent.addTool(tool3); // Still works!
agent.removeTool('tool1'); // Still works!
agent.setTools([newTools]); // Still works!
agent.listTools(); // Still works!
// New API via .tools property
agent.tools.disable('tool2'); // NEW!
agent.tools.enable('tool2'); // NEW!
Best Practices
1. Use Namespaces for Organization
// Good
agent.tools.register(githubTool, { namespace: 'github' });
agent.tools.register(slackTool, { namespace: 'slack' });
agent.tools.register(databaseTool, { namespace: 'database' });
// Bad
agent.tools.register(githubTool); // Hard to organize later
2. Use Priorities for Selection Ordering
// Good
agent.tools.register(primaryAPI, { priority: 100 });
agent.tools.register(fallbackAPI, { priority: 50 });
// Without explicit priorities, do not assume an application fallback order
agent.tools.register(primaryAPI);
agent.tools.register(fallbackAPI);
3. Disable Destructive Tools by Default
// Good
agent.tools.register(deleteTool, {
enabled: false, // Disabled by default
namespace: 'destructive',
});
// Enable only when needed
function enableDestructiveMode() {
agent.tools.enable('delete_tool');
}
4. Use Conditions for Complex Logic
// Good
agent.tools.register(adminTool, {
conditions: [
{ type: 'custom', predicate: (ctx) => ctx.custom?.role === 'admin' && ctx.custom?.environment === 'production' },
],
});
// Bad - manual checking everywhere
if (user.role === 'admin') {
agent.tools.enable('admin_tool');
} else {
agent.tools.disable('admin_tool');
}
5. Persist Tool State for Sessions
// Save tool state with session
const toolState = agent.tools.getState();
session.customData = { ...session.customData, toolState };
await sessionManager.save(session);
// Restore tool state
const loaded = await sessionManager.load(sessionId);
if (loaded?.customData?.toolState) {
agent.tools.loadState(loaded.customData.toolState);
}
Circuit Breaker Protection
ToolManager includes built-in circuit breaker protection for each tool. When a tool fails repeatedly, the circuit breaker prevents further calls to avoid cascading failures.
// Get circuit breaker states for all tools
const states = agent.tools.getCircuitBreakerStates();
// Returns: Map<toolName, 'closed' | 'open' | 'half-open'>
for (const [toolName, state] of states) {
console.log(`${toolName}: ${state}`);
}
// Get metrics for a specific tool
const metrics = agent.tools.getToolCircuitBreakerMetrics('risky_tool');
console.log(`Successes: ${metrics.successCount}, Failures: ${metrics.failureCount}`);
// Manually reset a circuit breaker
agent.tools.resetToolCircuitBreaker('risky_tool');
Configure circuit breaker per tool:
agent.tools.setCircuitBreakerConfig('external_api', {
failureThreshold: 3, // Open after 3 failures
successThreshold: 2, // Close after 2 successes in half-open
resetTimeoutMs: 60000, // Try half-open after 60s
windowMs: 300000, // Track failures in 5 min window
});
Circuit breaker states:
- Closed (normal) - Tool executes normally
- Open (tripped) - Tool calls fail immediately without execution
- Half-Open (testing) - One call allowed to test recovery
Tool Execution
ToolManager implements IToolExecutor for direct tool execution:
// Execute tool directly (used internally by agentic loop)
const result = await agent.tools.execute('get_weather', { location: 'Paris' });
// Execute returns the tool's result or throws on error