diff --git a/changelog.md b/changelog.md
index 670f555..15903ae 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,15 @@
# Changelog
+## 2026-01-20 - 1.8.0 - feat(tools)
+add ToolRegistry, ToolSearchTool and ExpertTool to support on-demand tool visibility, discovery, activation, and expert/subagent tooling; extend DualAgentOrchestrator API and interfaces to manage tool lifecycle
+
+- Introduce ToolRegistry to manage tool registration, visibility (initial vs on-demand), activation, initialization, cleanup, and search
+- Add ToolSearchTool providing search/list/activate/details actions for discovering and enabling on-demand tools
+- Add ExpertTool to wrap a DualAgentOrchestrator as a sub-agent (expert) tool and the IExpertConfig interface
+- Extend DualAgentOrchestrator API: registerTool(tool, options?), registerExpert(config), enableToolSearch(), getRegistry(); registerStandardTools/start now initialize visible tools via registry
+- Add IToolRegistrationOptions and IToolMetadata/IExpertConfig types to smartagent.interfaces and export ToolRegistry/ToolSearchTool/ExpertTool in public entry points
+- Documentation updates (readme) describing tool visibility, tool search, and expert/subagent system
+
## 2026-01-20 - 1.7.0 - feat(docs)
document native tool calling support and update README to clarify standard and additional tools
diff --git a/readme.hints.md b/readme.hints.md
index 0828b57..b11f84b 100644
--- a/readme.hints.md
+++ b/readme.hints.md
@@ -7,6 +7,7 @@
- **DualAgentOrchestrator**: Main entry point, coordinates Driver and Guardian agents
- **DriverAgent**: Reasons about tasks, plans steps, proposes tool calls (supports both XML and native tool calling)
- **GuardianAgent**: Evaluates tool calls against configured policies
+- **ToolRegistry**: Manages tool lifecycle, visibility, and discovery
- **BaseToolWrapper**: Base class for creating custom tools
- **plugins.ts**: Imports and re-exports smartai and other dependencies
@@ -19,6 +20,49 @@
## Additional Tools (must register manually)
6. **JsonValidatorTool** - JSON validation and formatting (NOT in registerStandardTools)
+7. **ToolSearchTool** - AI-facing interface for tool discovery and activation
+8. **ExpertTool** - Wraps a DualAgentOrchestrator as a specialized expert tool
+
+## Tool Visibility System
+Tools can be registered with visibility modes:
+- **initial**: Always visible to Driver, included in system prompt (default)
+- **on-demand**: Only discoverable via search, must be activated before use
+
+```typescript
+// Register with visibility options
+orchestrator.registerTool(myTool, {
+ visibility: 'on-demand',
+ tags: ['database', 'sql'],
+ category: 'data'
+});
+```
+
+## Expert/SubAgent System
+Experts are specialized agents wrapped as tools, enabling hierarchical agent architectures:
+
+```typescript
+orchestrator.registerExpert({
+ name: 'code_reviewer',
+ description: 'Reviews code for quality and best practices',
+ systemMessage: 'You are a code review expert...',
+ guardianPolicy: 'Allow read-only file access',
+ tools: [new FilesystemTool()],
+ visibility: 'on-demand',
+ tags: ['code', 'review']
+});
+```
+
+## Tool Search
+Enable tool discovery for the Driver:
+
+```typescript
+orchestrator.enableToolSearch();
+// Driver can now use:
+// - tools.search({"query": "database"})
+// - tools.list({})
+// - tools.activate({"name": "database_expert"})
+// - tools.details({"name": "filesystem"})
+```
## Key Features
- Token streaming support (`onToken` callback)
@@ -28,6 +72,9 @@
- Result truncation with configurable limits
- History windowing to manage token usage
- **Native tool calling mode** (`useNativeToolCalling: true`) for providers like Ollama
+- **Tool visibility system** (initial vs on-demand)
+- **Expert/SubAgent system** for hierarchical agents
+- **Tool search and discovery** via ToolSearchTool
## Native Tool Calling
When `useNativeToolCalling` is enabled:
diff --git a/readme.md b/readme.md
index d051160..c25532b 100644
--- a/readme.md
+++ b/readme.md
@@ -37,19 +37,19 @@ flowchart TB
end
subgraph Orchestrator["DualAgentOrchestrator"]
+ Registry["ToolRegistry
Visibility & Lifecycle"]
Driver["Driver Agent
Reason + Plan"]
Guardian["Guardian Agent
Evaluate against policy"]
Driver -->|"tool call proposal"| Guardian
Guardian -->|"approve / reject + feedback"| Driver
+ Registry -->|"visible tools"| Driver
end
- subgraph Tools["Standard Tools"]
- FS["Filesystem"]
- HTTP["HTTP"]
- Shell["Shell"]
- Browser["Browser"]
- Deno["Deno"]
+ subgraph Tools["Tools"]
+ Initial["Initial Tools
Always visible"]
+ OnDemand["On-Demand Tools
Discoverable via search"]
+ Experts["Expert SubAgents
Specialized agents as tools"]
end
Task --> Orchestrator
@@ -266,6 +266,157 @@ orchestrator.registerTool(new JsonValidatorTool());
```
+### 🔍 ToolSearchTool
+
+Enable the Driver to discover and activate on-demand tools at runtime.
+
+**Actions**: `search`, `list`, `activate`, `details`
+
+```typescript
+// Enable tool search (adds the 'tools' tool)
+orchestrator.enableToolSearch();
+```
+
+```typescript
+// Search for tools by capability
+
+ tools
+ search
+ {"query": "database"}
+
+
+// List all available tools
+
+ tools
+ list
+ {}
+
+
+// Activate an on-demand tool
+
+ tools
+ activate
+ {"name": "database_expert"}
+
+
+// Get detailed information about a tool
+
+ tools
+ details
+ {"name": "filesystem"}
+
+```
+
+### 🧠 ExpertTool (SubAgents)
+
+Create specialized sub-agents that can be invoked as tools. Experts are complete `DualAgentOrchestrator` instances wrapped as tools, enabling hierarchical agent architectures.
+
+**Actions**: `consult`
+
+```typescript
+// Register an expert for code review
+orchestrator.registerExpert({
+ name: 'code_reviewer',
+ description: 'Reviews code for quality, bugs, and best practices',
+ systemMessage: `You are an expert code reviewer. Analyze code for:
+ - Bugs and potential issues
+ - Code style and best practices
+ - Performance concerns
+ - Security vulnerabilities`,
+ guardianPolicy: 'Allow read-only file access within the workspace',
+ tools: [new FilesystemTool()],
+ visibility: 'on-demand', // Only available via tool search
+ tags: ['code', 'review', 'quality'],
+ category: 'expert',
+});
+```
+
+```typescript
+// Consult an expert
+
+ code_reviewer
+ consult
+ {
+ "task": "Review this function for potential issues",
+ "context": "This is a user authentication handler"
+ }
+
+```
+
+## 🎯 Tool Visibility System
+
+SmartAgent supports **tool visibility modes** for scalable agent architectures:
+
+- **`initial`** (default): Tool is visible to the Driver from the start, included in the system prompt
+- **`on-demand`**: Tool is hidden until explicitly activated via `tools.activate()`
+
+This enables you to have many specialized tools/experts without overwhelming the Driver's context.
+
+```typescript
+// Register a tool with on-demand visibility
+orchestrator.registerTool(new MySpecializedTool(), {
+ visibility: 'on-demand',
+ tags: ['specialized', 'database'],
+ category: 'data',
+});
+
+// Enable tool search so Driver can discover and activate on-demand tools
+orchestrator.enableToolSearch();
+
+// The Driver can now:
+// 1. tools.search({"query": "database"}) -> finds MySpecializedTool
+// 2. tools.activate({"name": "myspecialized"}) -> enables it
+// 3. myspecialized.action({...}) -> use the tool
+```
+
+### Expert SubAgent Example
+
+```typescript
+const orchestrator = new DualAgentOrchestrator({
+ openaiToken: 'sk-...',
+ defaultProvider: 'openai',
+ guardianPolicyPrompt: 'Allow safe operations...',
+});
+
+orchestrator.registerStandardTools();
+orchestrator.enableToolSearch();
+
+// Initial expert (always visible)
+orchestrator.registerExpert({
+ name: 'code_assistant',
+ description: 'Helps with coding tasks and code generation',
+ systemMessage: 'You are a helpful coding assistant...',
+ guardianPolicy: 'Allow read-only file access',
+ tools: [new FilesystemTool()],
+});
+
+// On-demand experts (discoverable via search)
+orchestrator.registerExpert({
+ name: 'database_expert',
+ description: 'Database design, optimization, and query analysis',
+ systemMessage: 'You are a database expert...',
+ guardianPolicy: 'Allow read-only operations',
+ visibility: 'on-demand',
+ tags: ['database', 'sql', 'optimization'],
+});
+
+orchestrator.registerExpert({
+ name: 'security_auditor',
+ description: 'Security vulnerability assessment and best practices',
+ systemMessage: 'You are a security expert...',
+ guardianPolicy: 'Allow read-only file access',
+ visibility: 'on-demand',
+ tags: ['security', 'audit', 'vulnerabilities'],
+});
+
+await orchestrator.start();
+
+// Now the Driver can:
+// - Use code_assistant directly
+// - Search for "database" and activate database_expert when needed
+// - Search for "security" and activate security_auditor when needed
+```
+
## 🎥 Streaming Support
SmartAgent supports token-by-token streaming for real-time output during LLM generation:
@@ -608,12 +759,15 @@ const orchestrator = new DualAgentOrchestrator({
| `stop()` | Cleanup all tools and resources |
| `run(task, options?)` | Execute a task with optional images for vision |
| `continueTask(input)` | Continue a task with user input |
-| `registerTool(tool)` | Register a custom tool |
+| `registerTool(tool, options?)` | Register a custom tool with optional visibility settings |
| `registerStandardTools()` | Register all built-in tools (Filesystem, HTTP, Shell, Browser, Deno) |
| `registerScopedFilesystemTool(basePath, excludePatterns?)` | Register filesystem tool with path restriction |
+| `registerExpert(config)` | Register a specialized sub-agent as a tool |
+| `enableToolSearch()` | Enable tool discovery and activation for the Driver |
| `setGuardianPolicy(policy)` | Update Guardian policy at runtime |
| `getHistory()` | Get conversation history |
| `getToolNames()` | Get list of registered tool names |
+| `getRegistry()` | Get the ToolRegistry for advanced operations |
| `isActive()` | Check if orchestrator is running |
### Exports
@@ -624,6 +778,9 @@ export { DualAgentOrchestrator } from '@push.rocks/smartagent';
export { DriverAgent } from '@push.rocks/smartagent';
export { GuardianAgent } from '@push.rocks/smartagent';
+// Tool Registry
+export { ToolRegistry } from '@push.rocks/smartagent';
+
// Tools
export { BaseToolWrapper } from '@push.rocks/smartagent';
export { FilesystemTool, type IFilesystemToolOptions } from '@push.rocks/smartagent';
@@ -632,9 +789,11 @@ export { ShellTool } from '@push.rocks/smartagent';
export { BrowserTool } from '@push.rocks/smartagent';
export { DenoTool, type TDenoPermission } from '@push.rocks/smartagent';
export { JsonValidatorTool } from '@push.rocks/smartagent';
+export { ToolSearchTool } from '@push.rocks/smartagent';
+export { ExpertTool } from '@push.rocks/smartagent';
// Types and interfaces
-export * from '@push.rocks/smartagent'; // All interfaces
+export * from '@push.rocks/smartagent'; // All interfaces (IExpertConfig, IToolMetadata, etc.)
// Re-exported from @push.rocks/smartai
export { type ISmartAiOptions, type TProvider, type ChatMessage, type ChatOptions, type ChatResponse };
diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts
index fe2841e..0ae1061 100644
--- a/ts/00_commitinfo_data.ts
+++ b/ts/00_commitinfo_data.ts
@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartagent',
- version: '1.7.0',
+ version: '1.8.0',
description: 'an agentic framework built on top of @push.rocks/smartai'
}
diff --git a/ts/index.ts b/ts/index.ts
index 1bf53a7..ff94c9e 100644
--- a/ts/index.ts
+++ b/ts/index.ts
@@ -7,6 +7,9 @@ export { DualAgentOrchestrator } from './smartagent.classes.dualagent.js';
export { DriverAgent } from './smartagent.classes.driveragent.js';
export { GuardianAgent } from './smartagent.classes.guardianagent.js';
+// Export tool registry and related classes
+export { ToolRegistry } from './smartagent.classes.toolregistry.js';
+
// Export base tool class for custom tool creation
export { BaseToolWrapper } from './smartagent.tools.base.js';
@@ -18,6 +21,10 @@ export { BrowserTool } from './smartagent.tools.browser.js';
export { DenoTool, type TDenoPermission } from './smartagent.tools.deno.js';
export { JsonValidatorTool } from './smartagent.tools.json.js';
+// Export tool search and expert tools
+export { ToolSearchTool } from './smartagent.tools.search.js';
+export { ExpertTool } from './smartagent.tools.expert.js';
+
// Export all interfaces
export * from './smartagent.interfaces.js';
diff --git a/ts/smartagent.classes.dualagent.ts b/ts/smartagent.classes.dualagent.ts
index 65321f5..1e7651f 100644
--- a/ts/smartagent.classes.dualagent.ts
+++ b/ts/smartagent.classes.dualagent.ts
@@ -8,6 +8,9 @@ import { HttpTool } from './smartagent.tools.http.js';
import { ShellTool } from './smartagent.tools.shell.js';
import { BrowserTool } from './smartagent.tools.browser.js';
import { DenoTool } from './smartagent.tools.deno.js';
+import { ToolRegistry } from './smartagent.classes.toolregistry.js';
+import { ToolSearchTool } from './smartagent.tools.search.js';
+import { ExpertTool } from './smartagent.tools.expert.js';
/**
* DualAgentOrchestrator - Coordinates Driver and Guardian agents
@@ -20,7 +23,7 @@ export class DualAgentOrchestrator {
private guardianProvider: plugins.smartai.MultiModalModel;
private driver: DriverAgent;
private guardian: GuardianAgent;
- private tools: Map = new Map();
+ private registry: ToolRegistry = new ToolRegistry();
private isRunning = false;
private conversationHistory: interfaces.IAgentMessage[] = [];
private ownsSmartAi = true; // true if we created the SmartAi instance, false if it was provided
@@ -125,19 +128,55 @@ export class DualAgentOrchestrator {
}
/**
- * Register a custom tool
+ * Register a custom tool with optional visibility settings
*/
- public registerTool(tool: BaseToolWrapper): void {
- this.tools.set(tool.name, tool);
- // Register with agents if they exist (they're created in start())
- if (this.driver) {
- this.driver.registerTool(tool);
- }
- if (this.guardian) {
- this.guardian.registerTool(tool);
+ public registerTool(
+ tool: BaseToolWrapper,
+ options?: interfaces.IToolRegistrationOptions
+ ): void {
+ this.registry.register(tool, options);
+
+ // If initial visibility and agents exist, register with them
+ const visibility = options?.visibility ?? 'initial';
+ if (visibility === 'initial') {
+ if (this.driver) {
+ this.driver.registerTool(tool);
+ }
+ if (this.guardian) {
+ this.guardian.registerTool(tool);
+ }
}
}
+ /**
+ * Register an expert (subagent) as a tool
+ */
+ public registerExpert(config: interfaces.IExpertConfig): void {
+ const expert = new ExpertTool(config, this.smartai);
+ this.registerTool(expert, {
+ visibility: config.visibility,
+ tags: config.tags,
+ category: config.category ?? 'expert',
+ });
+ }
+
+ /**
+ * Enable tool search functionality
+ * This adds a 'tools' tool that allows the Driver to discover and activate on-demand tools
+ */
+ public enableToolSearch(): void {
+ const searchTool = new ToolSearchTool(this.registry, (tool) => {
+ // Callback when an on-demand tool is activated
+ if (this.driver) {
+ this.driver.registerTool(tool);
+ }
+ if (this.guardian) {
+ this.guardian.registerTool(tool);
+ }
+ });
+ this.registerTool(searchTool); // Always initial visibility
+ }
+
/**
* Register all standard tools
*/
@@ -193,19 +232,14 @@ export class DualAgentOrchestrator {
});
this.guardian = new GuardianAgent(this.guardianProvider, this.options.guardianPolicyPrompt);
- // Register any tools that were added before start() with the agents
- for (const tool of this.tools.values()) {
+ // Register visible tools with agents
+ for (const tool of this.registry.getVisibleTools()) {
this.driver.registerTool(tool);
this.guardian.registerTool(tool);
}
- // Initialize all tools
- const initPromises: Promise[] = [];
- for (const tool of this.tools.values()) {
- initPromises.push(tool.initialize());
- }
-
- await Promise.all(initPromises);
+ // Initialize visible tools
+ await this.registry.initializeVisibleTools();
this.isRunning = true;
}
@@ -213,13 +247,7 @@ export class DualAgentOrchestrator {
* Cleanup all tools
*/
public async stop(): Promise {
- const cleanupPromises: Promise[] = [];
-
- for (const tool of this.tools.values()) {
- cleanupPromises.push(tool.cleanup());
- }
-
- await Promise.all(cleanupPromises);
+ await this.registry.cleanup();
// Only stop smartai if we created it (don't stop external instances)
if (this.ownsSmartAi) {
@@ -432,7 +460,7 @@ Please output the exact XML format above.`
});
// Execute the tool
- const tool = this.tools.get(proposal.toolName);
+ const tool = this.registry.getTool(proposal.toolName);
if (!tool) {
const errorMessage = `Tool "${proposal.toolName}" not found.`;
driverResponse = await this.driver.continueWithMessage(
@@ -652,6 +680,13 @@ Please output the exact XML format above.`
* Get registered tool names
*/
public getToolNames(): string[] {
- return Array.from(this.tools.keys());
+ return this.registry.getAllMetadata().map((m) => m.name);
+ }
+
+ /**
+ * Get the tool registry for advanced operations
+ */
+ public getRegistry(): ToolRegistry {
+ return this.registry;
}
}
diff --git a/ts/smartagent.classes.toolregistry.ts b/ts/smartagent.classes.toolregistry.ts
new file mode 100644
index 0000000..bc67582
--- /dev/null
+++ b/ts/smartagent.classes.toolregistry.ts
@@ -0,0 +1,188 @@
+import * as interfaces from './smartagent.interfaces.js';
+import { BaseToolWrapper } from './smartagent.tools.base.js';
+
+/**
+ * ToolRegistry - Manages tool registration, visibility, and lifecycle
+ *
+ * Responsibilities:
+ * - Track all registered tools with their metadata
+ * - Manage visibility (initial vs on-demand)
+ * - Handle activation of on-demand tools
+ * - Provide search functionality
+ */
+export class ToolRegistry {
+ private tools: Map = new Map();
+ private metadata: Map = new Map();
+ private activated: Set = new Set();
+
+ /**
+ * Register a tool with optional visibility settings
+ */
+ register(tool: BaseToolWrapper, options: interfaces.IToolRegistrationOptions = {}): void {
+ const visibility = options.visibility ?? 'initial';
+
+ this.tools.set(tool.name, tool);
+ this.metadata.set(tool.name, {
+ name: tool.name,
+ description: tool.description,
+ actions: tool.actions,
+ visibility,
+ isActivated: visibility === 'initial',
+ isInitialized: false,
+ tags: options.tags,
+ category: options.category,
+ });
+
+ if (visibility === 'initial') {
+ this.activated.add(tool.name);
+ }
+ }
+
+ /**
+ * Get tools visible to the Driver (initial + activated on-demand)
+ */
+ getVisibleTools(): BaseToolWrapper[] {
+ return Array.from(this.tools.entries())
+ .filter(([name]) => this.activated.has(name))
+ .map(([, tool]) => tool);
+ }
+
+ /**
+ * Get all tools (for search results)
+ */
+ getAllTools(): BaseToolWrapper[] {
+ return Array.from(this.tools.values());
+ }
+
+ /**
+ * Get a specific tool by name
+ */
+ getTool(name: string): BaseToolWrapper | undefined {
+ return this.tools.get(name);
+ }
+
+ /**
+ * Get metadata for a tool
+ */
+ getMetadata(name: string): interfaces.IToolMetadata | undefined {
+ return this.metadata.get(name);
+ }
+
+ /**
+ * Get all metadata
+ */
+ getAllMetadata(): interfaces.IToolMetadata[] {
+ return Array.from(this.metadata.values());
+ }
+
+ /**
+ * Search tools by query (matches name, description, tags, action names)
+ */
+ search(query: string): interfaces.IToolMetadata[] {
+ const q = query.toLowerCase();
+ return this.getAllMetadata().filter((meta) => {
+ if (meta.name.toLowerCase().includes(q)) return true;
+ if (meta.description.toLowerCase().includes(q)) return true;
+ if (meta.tags?.some((t) => t.toLowerCase().includes(q))) return true;
+ if (meta.category?.toLowerCase().includes(q)) return true;
+ if (
+ meta.actions.some(
+ (a) => a.name.toLowerCase().includes(q) || a.description.toLowerCase().includes(q)
+ )
+ )
+ return true;
+ return false;
+ });
+ }
+
+ /**
+ * Activate an on-demand tool
+ */
+ async activate(name: string): Promise<{ success: boolean; error?: string }> {
+ const tool = this.tools.get(name);
+ const meta = this.metadata.get(name);
+
+ if (!tool || !meta) {
+ return { success: false, error: `Tool "${name}" not found` };
+ }
+
+ if (this.activated.has(name)) {
+ return { success: true }; // Already activated
+ }
+
+ // Initialize if not already initialized
+ if (!meta.isInitialized) {
+ await tool.initialize();
+ meta.isInitialized = true;
+ }
+
+ this.activated.add(name);
+ meta.isActivated = true;
+
+ return { success: true };
+ }
+
+ /**
+ * Check if a tool is activated
+ */
+ isActivated(name: string): boolean {
+ return this.activated.has(name);
+ }
+
+ /**
+ * Initialize all initial (visible) tools
+ */
+ async initializeVisibleTools(): Promise {
+ const promises: Promise[] = [];
+
+ for (const [name, tool] of this.tools) {
+ const meta = this.metadata.get(name);
+ if (meta && this.activated.has(name) && !meta.isInitialized) {
+ promises.push(
+ tool.initialize().then(() => {
+ meta.isInitialized = true;
+ })
+ );
+ }
+ }
+
+ await Promise.all(promises);
+ }
+
+ /**
+ * Cleanup all initialized tools
+ */
+ async cleanup(): Promise {
+ const promises: Promise[] = [];
+
+ for (const [name, tool] of this.tools) {
+ const meta = this.metadata.get(name);
+ if (meta?.isInitialized) {
+ promises.push(tool.cleanup());
+ }
+ }
+
+ await Promise.all(promises);
+ }
+
+ /**
+ * Check if a tool exists in the registry
+ */
+ has(name: string): boolean {
+ return this.tools.has(name);
+ }
+
+ /**
+ * Get the number of registered tools
+ */
+ get size(): number {
+ return this.tools.size;
+ }
+
+ /**
+ * Get the number of activated tools
+ */
+ get activatedCount(): number {
+ return this.activated.size;
+ }
+}
diff --git a/ts/smartagent.interfaces.ts b/ts/smartagent.interfaces.ts
index 3d3363e..a193e73 100644
--- a/ts/smartagent.interfaces.ts
+++ b/ts/smartagent.interfaces.ts
@@ -1,5 +1,65 @@
import * as plugins from './plugins.js';
+// ================================
+// Tool Visibility & Registry Types
+// ================================
+
+/**
+ * Tool visibility mode
+ * - 'initial': Conveyed to model in system prompt AND discoverable via search
+ * - 'on-demand': Only discoverable via search, must be activated before use
+ */
+export type TToolVisibility = 'initial' | 'on-demand';
+
+/**
+ * Tool metadata for discovery and management
+ */
+export interface IToolMetadata {
+ name: string;
+ description: string;
+ actions: IToolAction[];
+ visibility: TToolVisibility;
+ isActivated: boolean;
+ isInitialized: boolean;
+ tags?: string[];
+ category?: string;
+}
+
+/**
+ * Options when registering a tool
+ */
+export interface IToolRegistrationOptions {
+ visibility?: TToolVisibility;
+ tags?: string[];
+ category?: string;
+}
+
+/**
+ * Configuration for creating an Expert (SubAgent)
+ */
+export interface IExpertConfig {
+ /** Unique name for the expert */
+ name: string;
+ /** Description of the expert's capabilities */
+ description: string;
+ /** System message defining expert behavior */
+ systemMessage: string;
+ /** Guardian policy for the expert's inner agent */
+ guardianPolicy: string;
+ /** AI provider (defaults to parent's provider) */
+ provider?: plugins.smartai.TProvider;
+ /** Tools available to this expert */
+ tools?: IAgentToolWrapper[];
+ /** Max iterations for expert tasks (default: 10) */
+ maxIterations?: number;
+ /** Visibility mode (default: 'initial') */
+ visibility?: TToolVisibility;
+ /** Searchable tags */
+ tags?: string[];
+ /** Category for grouping */
+ category?: string;
+}
+
// ================================
// Task Run Options
// ================================
diff --git a/ts/smartagent.tools.expert.ts b/ts/smartagent.tools.expert.ts
new file mode 100644
index 0000000..a8be7ae
--- /dev/null
+++ b/ts/smartagent.tools.expert.ts
@@ -0,0 +1,144 @@
+import * as plugins from './plugins.js';
+import * as interfaces from './smartagent.interfaces.js';
+import { BaseToolWrapper } from './smartagent.tools.base.js';
+
+// Forward declaration to avoid circular import at module load time
+// The actual import happens lazily in initialize()
+let DualAgentOrchestrator: typeof import('./smartagent.classes.dualagent.js').DualAgentOrchestrator;
+
+/**
+ * ExpertTool - A specialized agent wrapped as a tool
+ *
+ * Enables hierarchical agent architectures where the Driver can delegate
+ * complex tasks to specialized experts with their own tools and policies.
+ */
+export class ExpertTool extends BaseToolWrapper {
+ public name: string;
+ public description: string;
+ public actions: interfaces.IToolAction[] = [
+ {
+ name: 'consult',
+ description: 'Delegate a task or question to this expert',
+ parameters: {
+ type: 'object',
+ properties: {
+ task: { type: 'string', description: 'The task or question for the expert' },
+ context: { type: 'string', description: 'Additional context to help the expert' },
+ },
+ required: ['task'],
+ },
+ },
+ ];
+
+ private config: interfaces.IExpertConfig;
+ private smartAi: plugins.smartai.SmartAi;
+ private inner?: InstanceType;
+
+ constructor(config: interfaces.IExpertConfig, smartAi: plugins.smartai.SmartAi) {
+ super();
+ this.config = config;
+ this.smartAi = smartAi;
+ this.name = config.name;
+ this.description = config.description;
+ }
+
+ async initialize(): Promise {
+ // Lazy import to avoid circular dependency
+ if (!DualAgentOrchestrator) {
+ const module = await import('./smartagent.classes.dualagent.js');
+ DualAgentOrchestrator = module.DualAgentOrchestrator;
+ }
+
+ this.inner = new DualAgentOrchestrator({
+ smartAiInstance: this.smartAi, // Share SmartAi instance
+ defaultProvider: this.config.provider,
+ driverSystemMessage: this.config.systemMessage,
+ guardianPolicyPrompt: this.config.guardianPolicy,
+ maxIterations: this.config.maxIterations ?? 10,
+ });
+
+ // Register expert's tools
+ if (this.config.tools) {
+ for (const tool of this.config.tools) {
+ // Tools in the config are IAgentToolWrapper, but we need BaseToolWrapper
+ // Since all our tools extend BaseToolWrapper, this cast is safe
+ this.inner.registerTool(tool as BaseToolWrapper);
+ }
+ }
+
+ await this.inner.start();
+ this.isInitialized = true;
+ }
+
+ async cleanup(): Promise {
+ if (this.inner) {
+ await this.inner.stop();
+ this.inner = undefined;
+ }
+ this.isInitialized = false;
+ }
+
+ async execute(
+ action: string,
+ params: Record
+ ): Promise {
+ this.validateAction(action);
+ this.ensureInitialized();
+
+ const task = params.task as string;
+ const context = params.context as string | undefined;
+
+ const fullTask = context ? `Context: ${context}\n\nTask: ${task}` : task;
+
+ try {
+ const result = await this.inner!.run(fullTask);
+
+ return {
+ success: result.success,
+ result: {
+ response: result.result,
+ iterations: result.iterations,
+ status: result.status,
+ },
+ summary: result.success
+ ? `Expert "${this.name}" completed (${result.iterations} iterations)`
+ : `Expert "${this.name}" failed: ${result.status}`,
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Expert error: ${error instanceof Error ? error.message : String(error)}`,
+ };
+ }
+ }
+
+ getCallSummary(action: string, params: Record): string {
+ const task = params.task as string;
+ const preview = task.length > 60 ? task.substring(0, 60) + '...' : task;
+ return `Consult ${this.name}: "${preview}"`;
+ }
+
+ getToolExplanation(): string {
+ return `## Expert: ${this.name}
+${this.description}
+
+### Usage:
+Delegate tasks to this expert when you need specialized help.
+
+\`\`\`
+
+ ${this.name}
+ consult
+ {"task": "Your question or task", "context": "Optional background"}
+
+\`\`\`
+`;
+ }
+
+ /**
+ * Get the expert's configuration
+ */
+ getConfig(): interfaces.IExpertConfig {
+ return this.config;
+ }
+}
diff --git a/ts/smartagent.tools.search.ts b/ts/smartagent.tools.search.ts
new file mode 100644
index 0000000..72e18a1
--- /dev/null
+++ b/ts/smartagent.tools.search.ts
@@ -0,0 +1,237 @@
+import * as interfaces from './smartagent.interfaces.js';
+import { BaseToolWrapper } from './smartagent.tools.base.js';
+import { ToolRegistry } from './smartagent.classes.toolregistry.js';
+
+/**
+ * ToolSearchTool - AI-facing interface for discovering and activating tools
+ *
+ * This tool enables the Driver to:
+ * - Search for tools by capability
+ * - List all available tools
+ * - Activate on-demand tools
+ * - Get detailed information about specific tools
+ */
+export class ToolSearchTool extends BaseToolWrapper {
+ public name = 'tools';
+ public description =
+ 'Search for and activate available tools and experts. Use this to discover specialized capabilities.';
+
+ public actions: interfaces.IToolAction[] = [
+ {
+ name: 'search',
+ description: 'Search for tools by name, description, tags, or capabilities',
+ parameters: {
+ type: 'object',
+ properties: {
+ query: { type: 'string', description: 'Search query' },
+ },
+ required: ['query'],
+ },
+ },
+ {
+ name: 'list',
+ description: 'List all available tools grouped by visibility',
+ parameters: { type: 'object', properties: {} },
+ },
+ {
+ name: 'activate',
+ description: 'Activate an on-demand tool to make it available for use',
+ parameters: {
+ type: 'object',
+ properties: {
+ name: { type: 'string', description: 'Name of the tool to activate' },
+ },
+ required: ['name'],
+ },
+ },
+ {
+ name: 'details',
+ description: 'Get detailed information about a specific tool',
+ parameters: {
+ type: 'object',
+ properties: {
+ name: { type: 'string', description: 'Name of the tool' },
+ },
+ required: ['name'],
+ },
+ },
+ ];
+
+ private registry: ToolRegistry;
+ private onToolActivated?: (tool: BaseToolWrapper) => void;
+
+ constructor(registry: ToolRegistry, onToolActivated?: (tool: BaseToolWrapper) => void) {
+ super();
+ this.registry = registry;
+ this.onToolActivated = onToolActivated;
+ }
+
+ async initialize(): Promise {
+ this.isInitialized = true;
+ }
+
+ async cleanup(): Promise {
+ this.isInitialized = false;
+ }
+
+ async execute(
+ action: string,
+ params: Record
+ ): Promise {
+ this.validateAction(action);
+
+ switch (action) {
+ case 'search':
+ return this.handleSearch(params.query as string);
+ case 'list':
+ return this.handleList();
+ case 'activate':
+ return this.handleActivate(params.name as string);
+ case 'details':
+ return this.handleDetails(params.name as string);
+ default:
+ return { success: false, error: `Unknown action: ${action}` };
+ }
+ }
+
+ private handleSearch(query: string): interfaces.IToolExecutionResult {
+ const results = this.registry.search(query);
+ return {
+ success: true,
+ result: results.map((m) => ({
+ name: m.name,
+ description: m.description,
+ visibility: m.visibility,
+ isActivated: m.isActivated,
+ category: m.category,
+ tags: m.tags,
+ actionCount: m.actions.length,
+ })),
+ summary: `Found ${results.length} tools matching "${query}"`,
+ };
+ }
+
+ private handleList(): interfaces.IToolExecutionResult {
+ const all = this.registry.getAllMetadata();
+ const initial = all.filter((m) => m.visibility === 'initial');
+ const onDemand = all.filter((m) => m.visibility === 'on-demand');
+
+ return {
+ success: true,
+ result: {
+ initial: initial.map((m) => ({
+ name: m.name,
+ description: m.description,
+ category: m.category,
+ })),
+ onDemand: onDemand.map((m) => ({
+ name: m.name,
+ description: m.description,
+ category: m.category,
+ isActivated: m.isActivated,
+ })),
+ summary: `${initial.length} initial, ${onDemand.length} on-demand`,
+ },
+ };
+ }
+
+ private async handleActivate(name: string): Promise {
+ const result = await this.registry.activate(name);
+
+ if (result.success && this.onToolActivated) {
+ const tool = this.registry.getTool(name);
+ if (tool) {
+ this.onToolActivated(tool);
+ }
+ }
+
+ return {
+ success: result.success,
+ result: result.success ? { name, message: `Tool "${name}" is now available` } : undefined,
+ error: result.error,
+ summary: result.success ? `Activated: ${name}` : result.error,
+ };
+ }
+
+ private handleDetails(name: string): interfaces.IToolExecutionResult {
+ const tool = this.registry.getTool(name);
+ const meta = this.registry.getMetadata(name);
+
+ if (!tool || !meta) {
+ return { success: false, error: `Tool "${name}" not found` };
+ }
+
+ return {
+ success: true,
+ result: {
+ name: meta.name,
+ description: meta.description,
+ visibility: meta.visibility,
+ isActivated: meta.isActivated,
+ category: meta.category,
+ tags: meta.tags,
+ actions: meta.actions,
+ fullExplanation: tool.getToolExplanation(),
+ },
+ };
+ }
+
+ getCallSummary(action: string, params: Record): string {
+ switch (action) {
+ case 'search':
+ return `Search tools: "${params.query}"`;
+ case 'list':
+ return 'List all tools';
+ case 'activate':
+ return `Activate tool: ${params.name}`;
+ case 'details':
+ return `Get details: ${params.name}`;
+ default:
+ return `tools.${action}`;
+ }
+ }
+
+ getToolExplanation(): string {
+ return `## Tool: tools
+Search for and manage available tools and experts.
+
+### Actions:
+
+**search** - Find tools by capability
+\`\`\`
+
+ tools
+ search
+ {"query": "database"}
+
+\`\`\`
+
+**list** - List all tools grouped by visibility
+\`\`\`
+
+ tools
+ list
+ {}
+
+\`\`\`
+
+**activate** - Activate an on-demand tool
+\`\`\`
+
+ tools
+ activate
+ {"name": "database_expert"}
+
+\`\`\`
+
+**details** - Get full information about a tool
+\`\`\`
+
+ tools
+ details
+ {"name": "filesystem"}
+
+\`\`\`
+`;
+ }
+}