Skip to content

Tool calls silently dropped after AgentTask is spawned #1264

@marvin-goesmann

Description

@marvin-goesmann

Describe the bug

When a tool's execute function spawns an AgentTask via task.run(), tool calls from the LLM are silently dropped. This affects both turns within the task and turns on the parent agent after the task completes. The agent logs "received a tool call with toolChoice set to 'none', ignoring". The LLM has no signal that its output was discarded - it keeps retrying the same tool call while the caller hears dead air.

Hypothesis: This appears to be caused by functionCallStorage context from the spawning tool persisting into subsequent generateReply() calls, which forces toolChoice = "none".

Version

Steps to reproduce

// A task that asks a yes/no question, then calls a tool to record the answer
function createConsentTask(chatCtx?: llm.ChatContext): voice.AgentTask<boolean> {
  const task = new voice.AgentTask<boolean>({
    chatCtx,
    instructions: "Ask the user if they consent to recording. Call recordConsent with their answer.",
    tools: {
      recordConsent: llm.tool({
        description: "Record the user's consent answer",
        parameters: z.object({ consented: z.boolean() }),
        execute: async ({ consented }) => {
          task.complete(consented);
          return undefined;
        },
      }),
    },
  });
  return task;
}

// Parent agent tool that spawns the task
const checkConsent = llm.tool({
  description: "Check recording consent",
  parameters: z.object({ reason: z.string() }),
  execute: async (_params, { ctx }) => {
    const task = createConsentTask(ctx.session.chatCtx);
    await task.run();
    return { result: "done" };
  },
});

// Flow:
// 1. LLM calls checkConsent → task starts, asks "Do you consent to recording?"
// 2. User says "Yes" → LLM tries to call recordConsent → silently dropped
//    (task hangs — workaround: override onUserTurnCompleted on the task)
// 3. After task.complete() returns to parent → LLM tries to call any tool
//    → silently dropped for the rest of the session

What we observed

Within the task: If the task asks the user a question and then needs to call a tool based on their answer (second turn), the tool call is dropped. The task appears to hang.

After the task completes: The parent agent continues but when it tries to call a tool, it's silently dropped.

Text input is unaffected: Sending a text message via session.run({ userInput }) works normally — tools are called. This path creates a fresh async context and calls generateReply() directly, bypassing onUserTurnCompleted. Only VAD-triggered voice turns are affected.

Confirmed in logs:

received a tool call with toolChoice set to 'none', ignoring
    function: "<function>"

Partial Workaround

Override onUserTurnCompleted on the task with explicit toolChoice: "auto" + StopResponse.

task.onUserTurnCompleted = async (chatCtx, newMessage) => {
  task.session.generateReply({ toolChoice: "auto", userInput: newMessage, chatCtx });
  throw new voice.StopResponse();
};

This doesn't fix it for the parent agent, and likely may cause other problems.

Environment

  • macOS Darwin 25.2.0 (arm64)
  • Node 24.10.0 with --no-async-context-frame
  • @livekit/agents 1.2.4

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions