Documentation/Dynamic Tool Management
v2.0.0-beta.1Snapshot bebdcd1Markdown ↗

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

typescript
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:

typescript
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

typescript
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:

typescript
// 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:

typescript
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:

typescript
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

typescript
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>;
}
typescript
// 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:

typescript
// 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:

typescript
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

typescript
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

typescript
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

typescript
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

typescript
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:

typescript
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

typescript
// 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

typescript
// 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

typescript
// 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

typescript
// 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

typescript
// 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.

typescript
// 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:

typescript
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:

typescript
// 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

Documentation snapshot · 2026-11-02Suggest a correction ↗

Search documentation

Type to search the v2 documentation.