### What’s actually happening 1. **The loader _is_ registering your tool** You should see a console line like: ``` ✓ Loaded dynamic tool 'inspirationalQuoteGeneratorTool' from 'dynamic/InspirationalQuoteGeneratorTool.ts' ``` That proves the object is stored in `dynamicToolRegistry`. 2. **The validator rejects the LLM’s action string** The key in the registry is **`inspirationalQuoteGeneratorTool`** The LLM emitted **`inspirationalquotegenerator`** → `allowedActions.includes("inspirationalquotegenerator")` is `false` → _“Action … is not a valid tool”_. So the bug is a **name-mismatch**, not a missing registration. --- ## 📏 Pick one naming convention and stick to it Let’s convert every class name to an **all-lowercase string with the “Tool” suffix stripped**, e.g.: ``` InspirationalQuoteGeneratorTool → inspirationalquotegenerator WordCountTool → wordcount ``` That way the key the loader stores **matches** what the LLM will read from the system prompt. --- ### 1. Add a helper ```ts function classToActionKey(className: string): string { return className.replace(/Tool$/, '').toLowerCase(); } ``` --- ### 2. Use it everywhere you register or expose a tool #### a) Loader (`loadExistingDynamicTools`) ```ts const actionName = classToActionKey(className); if (!this.dynamicToolRegistry.has(actionName)) { const ToolClass = require(`../tools/${path}`)[className]; if (ToolClass && ToolClass.prototype instanceof BaseTool) { const instance = new ToolClass(); // Tell the prompt generator what name the model must use (instance as any).name = actionName; this.dynamicToolRegistry.set(actionName, instance); console.info(`✓ registered '${actionName}'`); } } ``` #### b) Create-at-runtime flow (`CreateNewTool`) ```ts const actionKey = classToActionKey(toolName); // toolName is the class name agent.registerDynamicTool(actionKey, newToolInstance); ``` #### c) System-prompt generation If your `BaseTool` already has a `name` field that `getReactPrompt` reads, you’ve set it above. Otherwise just update the code that builds `allTools`: ```ts const allTools = this.getAllTools(); allTools.forEach(t => { if (!(t as any).name) (t as any).name = classToActionKey(t.constructor.name); }); return getReactPrompt(allTools, docSummaries, chatHistory); ``` _(Or be cleaner and extend `BaseTool` with a proper `public name: string`.)_ --- ### 3. (Optionally) normalise existing static tools Set their `name` property the same way when you instantiate them: ```ts this.tools.calculate = new CalculateTool(); (this.tools.calculate as any).name = 'calculate'; // already fine, but explicit ``` --- ### 4. Test 1. Hard-reload / restart dev-server so the new code is bundled. 2. Ask: “Give me an inspirational quote” again. Because the prompt now advertises **`inspirationalquotegenerator`** and the registry key matches, validation will pass and the tool will run. --- ## TL;DR Your tools are loading; the _keys_ don’t match the action the model outputs. Create a single `classToActionKey` helper, call it everywhere, and the mismatch disappears.