#nested agents

36 messages · Page 1 of 1 (latest)

carmine ridge
#

I have an Agent A that is not a pizza expert, He can call a tool which is an Agent B that is a pizza expert. Himself has tools, when I trigger the Agent A he seems to ask the Agent B but the agent B isn't using the tools at his disposable why?

woeful owlBOT
#

Hi @carmine ridge. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

When you structure Agent B (the pizza expert) as a tool that Agent A can call, it's important to ensure that Agent B is configured to use its own tools during the generation process. Based on the Convex documentation, here are some key points and possible reasons why Agent B's tools might not be invoked:

  1. How Agent B is Called as a Tool:
    If Agent B is implemented as a tool (using createTool) and its handler simply calls thread.generateText or similar, you must explicitly pass the tools you want Agent B to use at that call site. Tools can be specified at the agent, thread, or per-call level, and the most specific one takes precedence. If you don't pass the tools when calling generateText, Agent B may not have access to its tools during that invocation.

    Example from the docs:

    const agentTool = createTool({
      description: `Ask a question to agent ${agent.name}`,
      args: z.object({
        message: z.string().describe("The message to ask the agent"),
      }),
      handler: async (ctx, args, options): Promise<string> => {
        const { userId } = ctx;
        const { thread } = await agent.createThread(ctx, { userId });
        const result = await thread.generateText(
          {
            prompt: [...options.messages, { role: "user", content: args.message }],
            // You must pass tools here if you want Agent B to use them!
            tools: agent.options.tools, // <-- ensure tools are passed
          },
          { storageOptions: { saveMessages: "all" } },
        );
        return result.text;
      },
    });
    

    If you omit the tools parameter, Agent B will not have access to its tools during the generation step. Make sure you are passing them explicitly if needed.

#
  1. Tool Inheritance and Overriding:
    The tools available to an agent at any point are determined by the following precedence:
    args.tools ?? thread.tools ?? agent.options.tools
    This means that if you want Agent B to use its tools, you need to ensure they are set at the right level and not accidentally overridden or omitted when the tool call is made Agents > Tools.

  2. Agent as Tool Pattern:
    The documentation notes that when you use the "agent as a tool" pattern, each tool call typically works in an independent thread, and you need to manage the context and tool availability explicitly. If you want Agent B to use its tools, you must ensure the tool configuration is correct in the handler that invokes Agent B Using an LLM or Agent as a tool.

Summary:
Agent B is likely not using its tools because the tools are not being passed or configured correctly when Agent A calls Agent B as a tool. Double-check that you are passing Agent B's tools in the generateText (or similar) call within the tool handler. If you need Agent B to always have access to its tools, set them at the agent level or ensure they are passed at each invocation.

If you need more specific troubleshooting, please share the relevant code snippets for how Agent B is defined and how Agent A is calling it as a tool.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@carmine ridge Kapa.ai is still learning and improving, please let me know how I did by reacting below.

carmine ridge
#
import { createTool } from "@convex-dev/agent";
import z from "zod/v4";
import { pizzaioloAgent } from "../agents/pizzaiolo";
import { margheritaPizzaRecipe } from "./magherita";

const description = `Ask a question to agent ${pizzaioloAgent.options.name}. 
  An expert in Pizza making and recipes. He knows this: ${pizzaioloAgent.options.instructions}`

export const pizzaAgentTool = createTool({
  description,
  args: z.object({
    pizzaName: z.string().describe("The name of the pizza to discuss about"),
    message: z.string().describe(`${description}. This is the message to send to the agent`),
  }),
  handler: async (ctx, args, options): Promise<string> => {
    const { userId } = ctx;
    const { thread } = await pizzaioloAgent.createThread(ctx, { userId });

    const result = await thread.generateText(
      {
        // Pass through all messages from the current generation
        prompt: [...options.messages, { role: "user", content: args.message }],
      },
      // Save all the messages from the current generation to this thread.
      { storageOptions: { saveMessages: "none" } },
    );

    console.log("Chief called")

    // await saveThreadAsChild(ctx, ctx.threadId, thread.threadId);

    return result.text;
  },
});
import { components } from "../../_generated/api";
import { Agent } from "@convex-dev/agent";
import { openai } from "@ai-sdk/openai";
import { margheritaPizzaRecipe } from "../tools/magherita";

export const pizzaioloAgent = new Agent(components.agent, {
  name: "Pizzaiolo Agent",
  languageModel: openai.chat("gpt-5-nano"),
  instructions: `
  You are a pizza chief. You're an expert in Pizza making and recipes.
  Always use the tools at your disposal to answer the user, then give a concise and precise answer.
  Always make it short and precise. Never make up an answer if you don't know it.
  For a margherita pizza, always use the corresponding tool. 
  `,
  tools: { margheritaPizzaRecipe },
  maxSteps: 3,
});
#
import { createTool } from "@convex-dev/agent";
import z from "zod/v4";

export const margheritaPizzaRecipe = createTool({
  description: "Search for the correct margherita pizza recipe",
  args: z.object({
    query: z.string().describe("The query to get the margherita pizza recipe for (Example: ingredients, steps, origin...)"),
  }),
  handler: async (ctx, args, options) => {

    return "Buy it in a shop.";
  },
});

@woeful owl that's the structure

woeful owlBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Thank you for sharing your tool definition. Based on your code, margheritaPizzaRecipe is a tool created with createTool, and its handler currently just returns a static string:

handler: async (ctx, args, options) => {
  return "Buy it in a shop.";
}

If you want Agent B (the pizza expert) to use its own tools (such as margheritaPizzaRecipe) when called as a tool by Agent A, you need to ensure that:

  1. Agent B is configured with its tools when it is instantiated.
  2. When Agent A calls Agent B as a tool, the call to Agent B's generateText (or similar) must include the tools you want Agent B to use. If you do not pass the tools at the call site, Agent B will not have access to them during the LLM generation step.

From the documentation:

Specifying tools at each layer will overwrite the defaults. The tools will be args.tools ?? thread.tools ?? agent.options.tools. This allows you to create tools in a context that is convenient Agents > Tools.

Why Agent B's tools might not be used:

  • If you do not pass the tools when calling generateText inside the handler for Agent B (when used as a tool by Agent A), Agent B will not have access to its tools.
  • The handler you posted does not invoke any LLM or use any tools; it just returns a string. If you want Agent B to use its tools, you need to call generateText (or similar) and pass the tools.

Example:
If you want Agent B to use its tools, your handler should look something like this:

handler: async (ctx, args, options) => {
  const result = await agentB.generateText(
    ctx,
    { threadId: ctx.threadId },
    {
      prompt: args.query,
      tools: agentB.options.tools, // Ensure tools are passed here
    }
  );
  return result.text;
}
#

Summary:
Agent B is not using its tools because the handler does not invoke any LLM generation or pass tools. To enable tool usage, you must call generateText (or similar) and ensure the tools are provided at the correct level Using an LLM or Agent as a tool.

If you need a more concrete example or have a more complex setup, please provide additional code or context.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@carmine ridge Kapa.ai is still learning and improving, please let me know how I did by reacting below.

carmine ridge
#

why do I need to pass the tools since the agenB has the tools in his constructor ? @woeful owl

woeful owlBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

You need to pass the tools explicitly when calling generateText (or similar) because, according to the Convex documentation, tools can be specified at multiple levels: at the agent constructor, at the thread, or per-call. The actual set of tools available to the agent during a generation is determined by the following precedence:

args.tools ?? thread.tools ?? agent.options.tools

This means that if you do not pass tools at the call site (args.tools), the agent will fall back to the tools defined on the thread, and if those are not set, then to the tools defined in the agent's constructor (agent.options.tools). However, in some patterns—especially when using agents as tools or when creating new threads inside tool handlers—if you do not explicitly pass the tools, the context may not automatically inherit the agent's default tools, depending on how the thread or call is constructed.

From the documentation:

Specifying tools at each layer will overwrite the defaults. The tools will be args.tools ?? thread.tools ?? agent.options.tools. This allows you to create tools in a context that is convenient Agents > Tools.

So, to guarantee that Agent B has access to its tools during a nested call (such as when Agent A calls Agent B as a tool), you should explicitly pass the tools at the call site. This ensures there is no ambiguity or accidental omission due to context or thread boundaries.

If you do not pass the tools, and the thread or call context does not inherit them as expected, Agent B will not have access to its tools during that generation step.

#

In summary:
Passing the tools explicitly ensures Agent B always has access to them, regardless of how the call context is set up. This is especially important in nested agent/tool scenarios, as described in the Convex documentation.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@carmine ridge Kapa.ai is still learning and improving, please let me know how I did by reacting below.

carmine ridge
#

I ahve this log @woeful owl "Tool result without preceding tool call.. adding anyway"

woeful owlBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

The log message "Tool result without preceding tool call.. adding anyway" indicates that a tool result message was being added to the message history, but there was no corresponding tool call message found before it. This situation can occur if, for some reason, the tool result is being saved or processed without a matching tool call in the thread's message history.

While the knowledge sources do not provide a detailed explanation for this specific log message, they do mention a related utility function, filterOutOrphanedToolMessages, which is used to filter out tool call messages that don't have a corresponding tool result message Agents > Messages. This suggests that Convex is designed to handle cases where tool call and result messages might become mismatched, possibly due to errors or unusual execution flows.

If you are seeing this log frequently, it may indicate that tool results are being generated or saved in a way that is not paired with a tool call, which could be a sign of a bug or an unexpected sequence in your agent or tool logic.

I'm sorry, but the knowledge sources do not provide more detailed information about the root cause or implications of this log message. If this is causing issues in your application, you may want to review your tool call and result handling logic, or reach out to Convex support for further assistance.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@carmine ridge Kapa.ai is still learning and improving, please let me know how I did by reacting below.

carmine ridge
#

The tool doesnt save a message it just returns a response, it shouldnt be the case?

#

@woeful owl

woeful owlBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Sorry @carmine ridge, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist:

Perhaps try again in 15mins?

carmine ridge
#

@woeful owl The tool doesnt save a message it just returns a response, it shouldnt be the case?

woeful owlBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Based on the Convex documentation, when you define a tool using createTool, the tool's handler typically just returns a value (the tool result). The Agent infrastructure is responsible for saving the tool call and tool result as messages in the thread, not the tool handler itself. This is by design:

The tool call and result will be stored as messages in the thread associated with the source message. See Messages for more details.
Agents > Tools

So, your tool handler should just return the response (as you are doing), and the Agent will handle saving the tool call and result messages automatically. You do not need to manually save a message inside the tool handler.

If you are seeing issues with tool result messages not having a preceding tool call, it may be due to how the agent or thread is being managed, but it is not because your tool handler is supposed to save a message itself. The expected pattern is for the handler to return a result, and the Agent system manages message persistence.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@carmine ridge Kapa.ai is still learning and improving, please let me know how I did by reacting below.

carmine ridge
#

@woeful owl my agent triggers multiple time the same tool for no reason.

woeful owlBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

If your agent is triggering the same tool multiple times unexpectedly, there are a few possible causes based on the Convex documentation:

  1. Tool Call Retries:
    The Agent component supports automatic retries for tool calls if they fail. By default, the maximum number of retries is set to 3, but this can be configured via the callSettings option in your agent configuration. If a tool call fails or is perceived as incomplete, the agent may retry it, resulting in multiple invocations Customizing the agent.

  2. Step Count and Tool Call Loops:
    If you have set stopWhen: stepCountIs(n) with n > 1, the agent will continue to process tool calls and LLM steps until the step count is reached. This can cause the same tool to be called multiple times if the LLM's output continues to request it Agents > Tools.

  3. LLM Output Structure:
    Sometimes, the LLM may repeatedly request the same tool if the prompt or context leads it to believe the tool call is still needed. This can happen if the tool result is not being incorporated into the conversation context as expected, or if the LLM is not "satisfied" with the previous result.

  4. Idempotency and Retries:
    Convex actions (including those involving agents and tools) can fail due to transient errors (e.g., network issues, server restarts), and may be retried automatically. If your tool handler is not idempotent, this can result in repeated side effects Durable workflows and strong guarantees.

#

If none of these explanations fit your situation, the knowledge sources do not provide more specific troubleshooting steps for this issue. You may want to check your agent and tool configuration, review the logs for retry or error patterns, and ensure your tool handlers are idempotent and your prompts are structured to avoid repeated tool requests.

If the problem persists, consider reaching out to Convex support or providing more detailed logs or code snippets for further assistance.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@carmine ridge Kapa.ai is still learning and improving, please let me know how I did by reacting below.

carmine ridge
#

'Tool result without preceding tool call.. adding anyways' {
type: 'tool-result',
output: {
type: 'text',
value: 'Use tomato, that's all.'
},
toolCallId: 'call_02fBKkfJlNjHbiCDQfEDrmGD',
toolName: 'pizzaAgentTool'
}

I have this @woeful owl but it seems to me that there is a previous tool call... since there is a toolcallid

woeful owlBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Based on the knowledge sources, the log message 'Tool result without preceding tool call.. adding anyways' means that Convex is attempting to add a tool result message to the message history, but it cannot find a matching tool call message with the same toolCallId in the thread's message history.

Even though your tool result includes a toolCallId, Convex is not able to find a corresponding tool call message for that ID at the time it tries to add the result. This could happen if:

  • The tool call message was not saved correctly or was not yet persisted when the result was processed.
  • There is a race condition or ordering issue in how messages are being saved or retrieved.
  • The tool result is being processed in a different context or thread than the original tool call.

The documentation does not provide a detailed explanation for this specific log message or the internal matching logic, but it does mention that utilities like filterOutOrphanedToolMessages exist to handle mismatches between tool calls and results Agents > Messages.

Summary:
Even if you see a toolCallId in your tool result, Convex may log this message if it cannot find the corresponding tool call message in the message history at that moment. The knowledge sources do not provide more detailed troubleshooting steps for this scenario. If this is causing issues, you may want to review your message saving logic or reach out to Convex support for further assistance.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: