acoustic-pharmacist-34178
03/08/2026, 8:53 AM[04:50:08.947] DEBUG (22124): Agent handoff inserted into chat context
previousAgentId: "s_i_p_transfer_task"
newAgentId: "assistant"
but it seems none of the tools of the main agent is availabletall-airline-16756
03/08/2026, 8:54 AMtall-airline-16756
03/08/2026, 8:54 AMAgentTask, and why the main agent’s tools appear unavailable after returning from the task.
Short answer: You do not need to manually set the active agent back to the main agent. The framework automatically hands control back (your log shows that handoff: previousAgentId "s_i_p_transfer_task" → newAgentId "assistant").
Why your tools may seem unavailable
• Tool registration: Tools must be defined/attached to the main agent instance that regains control. If the agent instance that returns doesn’t have the tools registered, they won’t be available.
• Instance replacement or reinitialization: If your code replaces or reinitializes the main agent (or creates a new agent instance) during/after the task, the returned agent may lack the original tool bindings.
• Expected exclusive control during task: While an AgentTask runs, the task takes exclusive control of the session and the main agent’s tools are unavailable during that period. After completion, the main agent’s tools should be restored automatically.
Concrete debugging steps
1. Confirm tools are registered on the main agent
◦ Ensure your main agent declares its tools (e.g., with @function_tool in Python or llm.tool() in Node) or passes them via the tools parameter to its constructor.
1. Verify the same agent instance is returning
◦ Check for any code that might recreate or overwrite the main agent instance before/after the task completes.
◦ Log the agent instance identity (or a unique id) before the task, during task handoff, and after return to ensure it’s the same object with the same tool bindings.
1. Reproduce with a minimal example
◦ Create a small test that registers one simple tool on the main agent, starts an AgentTask, and then checks availability after the task completes. If the tool is missing there, it isolates the problem to agent instantiation/registration.
1. Inspect context/merging behavior
◦ Confirm your task isn’t modifying the agent’s instruction set or tool list as part of its execution or teardown. Tools should remain attached to the main agent after context merges back.
If you’d like, share:
• A minimal code snippet showing how you construct the main agent and register tools, and how you call the AgentTask.
• The logs around the task start, the handoff, and immediately after return (any lines that reference agent IDs or tool registration).
I can review the snippet and logs and point out exactly where tools might be getting detached or how to ensure they persist across the task boundary.
Sources: Agents and handoffs | LiveKit Documentation | Collect recording consent with tasks | LiveKit Documentation | Tool definition and use | LiveKit Documentationacoustic-pharmacist-34178
03/08/2026, 8:55 AMtall-airline-16756
03/08/2026, 8:56 AMagent and that agent's tools property.
Below are concise examples for both Python and Node.js and a few small checks you can add.
Python
# Get the active agent from the session
active_agent = session.agent
# Basic presence check
if not active_agent:
print("No active agent set on the session.")
else:
# Check tools (could be a dict or list)
tools = getattr(active_agent, "tools", None)
if tools:
print("Active agent has tools registered:", tools)
else:
print("Active agent has no tools.")
Node.js / TypeScript
// Get the active agent from the session
const activeAgent = session.agent;
if (!activeAgent) {
console.log("No active agent set on the session.");
} else {
const tools = (activeAgent as any).tools;
if (tools && Object.keys(tools).length > 0) {
console.log("Active agent has tools registered:", tools);
} else {
console.log("Active agent has no tools.");
}
}
Quick troubleshooting tips
• Log the agent identity too (e.g., agent.id or a custom identifier) before and after the task to ensure the same instance returns.
• If tools is empty after returning from an AgentTask, verify you aren’t accidentally recreating or replacing the main agent instance during task execution.
• Tools may be a dictionary or array depending on how you registered them; check both shapes when inspecting.
If you want, paste a minimal snippet showing how you construct/register tools for the main agent and how you start the AgentTask — I’ll point out exactly where to inspect or add logs to confirm the tools persist.
Sources: Agents and handoffs | LiveKit Documentation | Tool definition and use | LiveKit Documentationacoustic-pharmacist-34178
03/08/2026, 8:57 AMtall-airline-16756
03/08/2026, 8:57 AMsession.currentAgent. Below are a concise confirmation and a short example you can use to check whether that active agent has tools registered.
JavaScript / TypeScript
// Get the active agent from the session
const activeAgent = session.currentAgent;
if (!activeAgent) {
console.log("No active agent set on the session.");
} else {
const tools = (activeAgent as any).tools;
const hasTools = tools && (Array.isArray(tools) ? tools.length > 0 : Object.keys(tools).length > 0);
if (hasTools) {
console.log("Active agent has tools registered:", tools);
} else {
console.log("Active agent has no tools.");
}
}
Quick tips
• Log a unique agent identifier (for example activeAgent.id or a custom property) before the task, during handoff, and after return to confirm the same instance is active.
• Check both shapes for tools (array or object) since registration methods can produce either.
• If currentAgent.tools is empty after returning from an AgentTask, look for code that might recreate or overwrite the agent instance during the task boundary.
If you share a minimal snippet showing how you register tools and start the AgentTask, I can point out exactly where to add these checks or what else to log.acoustic-pharmacist-34178
03/08/2026, 9:01 AMthis.session.on(voice.AgentSessionEventTypes.FunctionToolsExecuted, (evt) => {
console.log(this.session.currentAgent.toolCtx);
});
the log shows it does have the toolstall-airline-16756
03/08/2026, 9:01 AMthis.session.currentAgent.toolCtx populated in the FunctionToolsExecuted event shows the active agent does have tools registered at that moment.
Next steps to narrow down why tools seem unavailable after returning from an `AgentTask`:
1. Confirm timeline with logs
◦ Add logs just before you start the AgentTask, inside the task (if possible), on the handoff event, and immediately after the task completes:
▪︎ Log session.currentAgent?.id (or other identifier)
▪︎ Log session.currentAgent?.toolCtx (or tools)
◦ This will show whether the same agent instance and tool context exist at every stage.
1. Check where you attempt to call tools
◦ Are you invoking tools via the agent instance (e.g., session.currentAgent) immediately after the task returns, or from some other object/context? If calls run from a stale reference to the agent, they may appear unavailable.
◦ Ensure any stored references to the agent were not captured before the task and then mutated/replaced.
1. Verify tool context vs. runtime availability
◦ toolCtx (or tools) showing in the event means tools are registered, but runtime execution may fail if:
▪︎ The caller lacks the proper binding to use toolCtx.
▪︎ The task altered permissions/state needed to invoke the tools.
◦ After the task completes, try a simple programmatic invocation of one registered tool (a no-op or test tool) to confirm execution works.
1. Minimal repro
◦ If the above logs still show the agent and tools are present but calls fail, create a minimal script:
i. Register a single test tool on the main agent.
ii. Start an AgentTask that returns quickly.
iii. After return, read session.currentAgent.toolCtx and immediately invoke the test tool.
◦ If the test tool runs, the problem is likely in your larger app’s lifecycle or how you reference the agent. If it fails, paste the minimal snippet and logs and I’ll examine it.
If you’d like, paste:
• The logs for the sequence (before task, during task, handoff, after return).
• The small snippet that invokes a tool after return.
I’ll review those and point out exactly where the reference/state might be getting lost.acoustic-pharmacist-34178
03/08/2026, 9:03 AMclass SIPTransferTask extends voice.AgentTask<string> {
constructor(private config: TransferToolConfigSchema, private destination: string) {
super({instructions: 'Performing SIP transfer...', allowInterruptions: true});
}
override async onEnter(): Promise<void> {
if (this.config.speak_during_execution && this.config?.speak_message) {
if (this.config.speak_type === 'prompt') {
this.session.generateReply({
instructions: this.config.speak_message,
allowInterruptions: true,
})
} else {
this.session.say(this.config.speak_message, { allowInterruptions: true });
}
}
const { room } = getJobContext();
try {
await transferSipCall(room, this.destination)
this.complete(`Transfer initiated to ${this.destination}.`);
} catch (err) {
const message = err instanceof Error ? err.message : 'SIP transfer failed.';
this.complete(`Transfer could not be completed: ${message}`);
}
}
}
llm.tool({
description: 'Perform a blind (cold) transfer of the current call to the given phone number, extension.',
parameters: schema,
execute: async ({ destination, }, { ctx }) => {
return runInToolContext(async () => {
console.log(`Initiating SIP transfer to ${destination} runIn options:`, config);
await ctx.waitForPlayout();
return new SIPTransferTask(config, destination).run();
})
},
});tall-airline-16756
03/08/2026, 9:03 AMSIPTransferTask from a tool.
Short answer
• The framework automatically returns control to the main agent when an AgentTask completes, and the main agent’s tools should remain registered. You do not need to manually set the active agent back. If tools appear missing after your SIPTransferTask, that’s caused by something in the execution path (session/agent mutation, timing/cleanup, or tool-context handling), not by needing to reset the agent.
What to check next (concise, actionable)
1. Add targeted logs around the full lifecycle
◦ Log these values at each stage:
▪︎ Before calling the tool: session.currentAgent?.id and session.currentAgent?.toolCtx (or tools)
▪︎ Inside runInToolContext right before await ctx.waitForPlayout()
▪︎ In SIPTransferTask.onEnter at start and right before calling this.complete(...)
▪︎ On task completion/handoff event (FunctionToolsExecuted already shows something) and immediately after the task returns
◦ This will show whether the same agent instance and tool context exist throughout.
1. Confirm the agent instance is not being replaced
◦ Look for code that might reinstantiate or overwrite session.currentAgent (or the main agent object) during the transfer flow or in any callbacks invoked by transferSipCall.
◦ Instead of relying on a previously captured agent reference, always read session.currentAgent after the task returns.
1. Verify transferSipCall and cleanup don’t terminate the session
◦ If transferSipCall disconnects participants or otherwise ends the room/session, the agent session could be torn down and tools would be unavailable.
◦ Ensure any playout/transfer-related cleanup runs after the agent has fully resumed, or delay cleanup until after you’ve verified tool calls.
1. Examine runInToolContext
◦ Test the same flow without runInToolContext to rule it out as a cause:
▪︎ Replace the wrapper temporarily with direct creation/run of SIPTransferTask and compare results.
◦ If leaving it out fixes it, the issue is within how runInToolContext mutates session/agent state in your environment.
1. Reproduce with a minimal test
◦ Create a small repro:
i. Register a single test tool on the main agent that logs when invoked.
ii. From that tool, run a very small AgentTask that immediately completes.
iii. After return, log session.currentAgent.toolCtx and immediately invoke the test tool.
◦ If the test tool invocation fails here, paste the repro and logs and I’ll review precisely where state is lost.
Immediate checks you can paste here
• Logs showing: before-tool, inside task (start and just before complete), handoff, and right after return.
• The implementation of transferSipCall (does it disconnect or mutate session/agent?)
• Any code that runs on task completion or in callbacks (especially those that touch session or agent creation/assignment)
If you share those logs/snippets, I’ll point out the exact line(s) likely causing the tools to be lost and give a small code change to fix it.
Sources: Collect recording consent with tasks | LiveKit Documentation | Tasks and task groups | LiveKit Documentation | Agent speech and audio | LiveKit Documentationacoustic-pharmacist-34178
03/08/2026, 9:07 AMllm.tool({
description: 'Perform a blind (cold) transfer of the current call to the given phone number, extension.',
parameters: schema,
execute: async ({ destination, }, { ctx }) => {
return runInToolContext(async () => {
console.log(`Initiating SIP transfer to ${destination} runIn options:`, config);
await ctx.waitForPlayout();
// return new SIPTransferTask(config, destination).run();
return await transferSipCall(getJobContext().room, destination)
})
},
});tall-airline-16756
03/08/2026, 9:08 AMtransferSipCall directly but the main agent’s tools disappear only when you run the same logic inside an AgentTask. I’ll focus on what specifically differs between those two flows and give targeted checks and a minimal repro you can run to pinpoint the cause.
Why this difference matters (short)
• AgentTasks take exclusive control of the session while they run. That handoff/return is automatic, but anything the task does that mutates the session/agent (or triggers session teardown) can make tools look unavailable after return.
• Calling transferSipCall directly runs in the same agent context and avoids the task handoff mechanics, so you don’t hit whatever side effect is triggered by the task lifecycle.
Targeted checks to run now
1. Confirm the task is fully completed before you try to use tools again
◦ Right after new SIPTransferTask(...).run() resolves, log:
▪︎ session.currentAgent?.id
▪︎ session.currentAgent?.toolCtx (or tools)
◦ If those show tools present immediately after the promise resolves, the problem is likely elsewhere (e.g., race later in your app).
1. Log inside task lifecycle and callbacks
◦ Add logs at these points (include timestamps):
▪︎ Before you call the tool (start of execute)
▪︎ At the start of SIPTransferTask.onEnter
▪︎ Just before this.complete(...)
▪︎ In any onExit or task cleanup if present
▪︎ Immediately after the run() promise resolves in the tool execute function
◦ This will confirm whether this.complete(...) is hit, and when the framework performs the handoff.
1. Verify transferSipCall does not mutate session/agent or end the call/session
◦ Confirm it does not:
▪︎ Call any session tear-down, room disconnect, or agent re-creation.
▪︎ Reassign session.currentAgent or its internal state.
◦ If transfer ends the media/room, the agent session can be stopped and tools lost.
1. Test without runInToolContext and with direct call inside task
◦ Two quick variations:
a) In the tool execute, run the task without runInToolContext to see if runInToolContext is contributing. b) Inside SIPTransferTask.onEnter, call await transferSipCall(...) directly (instead of delegating elsewhere) and then this.complete(...).
• If either change fixes it, the wrapper or delegation path is the culprit.
1. Minimal repro to isolate framework vs app
◦ Register a trivial tool on the main agent that:
▪︎ Logs when invoked
▪︎ From that tool, run an AgentTask that does nothing but this.complete("ok")
▪︎ After run() resolves, immediately attempt to invoke the trivial tool again
◦ If the second invocation fails, it’s reproducible and likely framework-related. If it succeeds, the issue is in your SIP-specific code (e.g., transferSipCall or playout/cleanup).
Common root causes to look for
• The task or transfer function triggers disconnection or room cleanup that stops the session.
• runInToolContext or some callback reinitializes or replaces the agent instance (so the returned agent is not the same object with the same tool bindings).
• A race: some later code runs and clears or re-registers tools after the task completes.
If you want, paste these three small snippets/log sections and I’ll inspect them:
• The logs around start → task onEnter → this.complete → run() resolution → immediate post-return (include timestamps).
• The transferSipCall implementation (just the parts that manipulate session/room/participants).
• The minimal repro I described (or I can supply one if you want to run it).
I’ll point to the exact line that’s causing the tools to disappear and propose the smallest fix.