[GH-ISSUE #527] Feature Request: On-Demand Tool Discovery to Reduce Context Window Bloat #269

Open
opened 2026-06-05 17:21:22 -04:00 by yindo · 1 comment
Owner

Originally created by @RaheesAhmed on GitHub (May 7, 2026).
Original GitHub issue: https://github.com/langchain-ai/deepagentsjs/issues/527

Originally assigned to: @aolsenjazz on GitHub.

The problem

When you pass a large set of tools to createDeepAgent, every tool's name, description, and full JSON schema gets injected into the system prompt on every single request whether the agent ends up using that tool or not.

At small scale this is fine. But once you're wiring up 20+ tools (MCP servers, custom integrations, domain-specific utilities), the upfront token cost becomes significant and the agent's performance actually degrades. This is a well-documented behavior: models given too many tools at once make worse routing decisions.

Right now there's no built-in way around this. You either include everything and pay the cost, or manually split agents which adds orchestration overhead.

What I'm proposing

A single lightweight meta-tool discover_tool that replaces the bulk of upfront tool registration. Instead of injecting all tool schemas into context, you give the agent one tool to look up whatever it needs, on demand.

import { createDeepAgent } from "deepagents";
import { createToolRegistry } from "deepagents/tools";

const registry = createToolRegistry([
  fetchBinanceTool,
  sendWhatsappTool,
  querySupabaseTool,
  // ... 30 more
]);

const agent = createDeepAgent({
  tools: [registry.discoverTool],  // only ONE tool in context upfront
});

When the agent needs to fetch from Binance, it calls discover_tool("binance"), gets back the schema, then calls the real tool. The registry activates it for that turn.

What changes in context

Before:

[system prompt]
tools: fetch_binance (full schema)
       send_whatsapp (full schema)
       query_supabase (full schema)
       ... 25 more full schemas

After:

[system prompt]
tools: discover_tool  find and activate any available tool by name

The agent only loads what it actually needs. Everything else stays out of context.

How the registry could work

export function createToolRegistry(tools: StructuredTool[]) {
  const map = new Map(tools.map(t => [t.name, t]));
  const active = new Map<string, StructuredTool>();

  const discoverTool = tool(
    async ({ name }: { name: string }) => {
      const found = map.get(name);
      if (!found) {
        const available = [...map.keys()].join(", ");
        return `Tool "${name}" not found. Available: ${available}`;
      }
      active.set(name, found);
      return `Tool "${name}" is now available. Schema: ${JSON.stringify(found.schema)}`;
    },
    {
      name: "discover_tool",
      description:
        "Look up and activate a tool by name before using it. Call this first whenever you need a capability you haven't used yet in this session.",
      schema: z.object({ name: z.string() }),
    }
  );

  return { discoverTool, active };
}

Relationship to Skills

Skills are for loading domain knowledge and instructions on demand. This is different it's about tool schemas and capability registration. The two could work together well: a skill could call discover_tool internally as part of its setup.

Tradeoffs worth discussing

Adding a round-trip (discover then call) costs one extra LLM step per new tool. For long sessions with many tools this is worth it. For short sessions with 3-4 tools, standard registration is fine. It probably makes sense to let users configure a threshold below N tools, register normally; above N, use discovery.

Happy to implement this as a ToolRegistryMiddleware following the existing middleware pattern if the idea looks reasonable to maintainers.

Originally created by @RaheesAhmed on GitHub (May 7, 2026). Original GitHub issue: https://github.com/langchain-ai/deepagentsjs/issues/527 Originally assigned to: @aolsenjazz on GitHub. **The problem** When you pass a large set of tools to `createDeepAgent`, every tool's name, description, and full JSON schema gets injected into the system prompt on every single request whether the agent ends up using that tool or not. At small scale this is fine. But once you're wiring up 20+ tools (MCP servers, custom integrations, domain-specific utilities), the upfront token cost becomes significant and the agent's performance actually degrades. This is a well-documented behavior: models given too many tools at once make worse routing decisions. Right now there's no built-in way around this. You either include everything and pay the cost, or manually split agents which adds orchestration overhead. **What I'm proposing** A single lightweight meta-tool `discover_tool` that replaces the bulk of upfront tool registration. Instead of injecting all tool schemas into context, you give the agent one tool to look up whatever it needs, on demand. ```ts import { createDeepAgent } from "deepagents"; import { createToolRegistry } from "deepagents/tools"; const registry = createToolRegistry([ fetchBinanceTool, sendWhatsappTool, querySupabaseTool, // ... 30 more ]); const agent = createDeepAgent({ tools: [registry.discoverTool], // only ONE tool in context upfront }); ``` When the agent needs to fetch from Binance, it calls `discover_tool("binance")`, gets back the schema, then calls the real tool. The registry activates it for that turn. **What changes in context** Before: ``` [system prompt] tools: fetch_binance (full schema) send_whatsapp (full schema) query_supabase (full schema) ... 25 more full schemas ``` After: ``` [system prompt] tools: discover_tool find and activate any available tool by name ``` The agent only loads what it actually needs. Everything else stays out of context. **How the registry could work** ```ts export function createToolRegistry(tools: StructuredTool[]) { const map = new Map(tools.map(t => [t.name, t])); const active = new Map<string, StructuredTool>(); const discoverTool = tool( async ({ name }: { name: string }) => { const found = map.get(name); if (!found) { const available = [...map.keys()].join(", "); return `Tool "${name}" not found. Available: ${available}`; } active.set(name, found); return `Tool "${name}" is now available. Schema: ${JSON.stringify(found.schema)}`; }, { name: "discover_tool", description: "Look up and activate a tool by name before using it. Call this first whenever you need a capability you haven't used yet in this session.", schema: z.object({ name: z.string() }), } ); return { discoverTool, active }; } ``` **Relationship to Skills** Skills are for loading domain knowledge and instructions on demand. This is different it's about tool schemas and capability registration. The two could work together well: a skill could call `discover_tool` internally as part of its setup. **Tradeoffs worth discussing** Adding a round-trip (discover then call) costs one extra LLM step per new tool. For long sessions with many tools this is worth it. For short sessions with 3-4 tools, standard registration is fine. It probably makes sense to let users configure a threshold below N tools, register normally; above N, use discovery. Happy to implement this as a `ToolRegistryMiddleware` following the existing middleware pattern if the idea looks reasonable to maintainers.
yindo added the enhancement label 2026-06-05 17:21:22 -04:00
Author
Owner

@aolsenjazz commented on GitHub (Jun 3, 2026):

Thanks for the request and suggestion @RaheesAhmed! We agree that this would be a good functionality to add and are taking a look! More to come very soon.

<!-- gh-comment-id:4612020709 --> @aolsenjazz commented on GitHub (Jun 3, 2026): Thanks for the request and suggestion @RaheesAhmed! We agree that this would be a good functionality to add and are taking a look! More to come very soon.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/deepagentsjs#269