acoustic-pharmacist-34178
03/06/2026, 6:30 PMtall-airline-16756
03/06/2026, 6:31 PMtall-airline-16756
03/06/2026, 6:31 PMRunContext is provided to function tools and contains the state for the current tool invocation (session, agent, and related metadata).
◦ When a tool is invoked, presence of a RunContext.function_call (or related fields on the RunContext) signals that a tool execution is active.
• Inspect run-level handles and interruption flags
◦ run_ctx.speech_handle.interrupted can be checked to detect interruptions to tool execution — this is useful for determining whether a running tool was interrupted.
◦ Long-running tools commonly expose asyncio tasks or cancellation tokens via the run context; checking whether those tasks are active indicates an ongoing tool run.
• Observe agent session events
◦ Agent sessions emit events such as conversation_item_added when a function tool is called or when the tool returns output. Listening for these events externally lets you monitor when tools start and finish.
◦ You can track a simple state machine: on function-call event → mark agent in tool context; on function-complete/output event → mark agent out of tool context.
• Combine approaches for robustness
◦ For short-lived tools, using RunContext.function_call plus the session events is usually sufficient.
◦ For long-running tools, track the asyncio task or cancellation token exposed in the run context and update the session state when that task completes or is cancelled.
If you’d like, tell me which environment you’re using (server-side Python/Node, or an SDK) and whether tools you care about are short-lived or long-running — I can give a short code sketch showing where to check run_ctx and how to subscribe to the conversation_item_added events.
Sources: Tool definition and use | LiveKit Documentation | Agent session | LiveKit Documentationacoustic-pharmacist-34178
03/06/2026, 6:33 PMtall-airline-16756
03/06/2026, 6:33 PMconversation_item_added (emitted when a function call starts or a function output is added).
◦ Also listen for events indicating function completion, cancellation, or errors.
1. Maintain an external state for each agent session
◦ When you detect a function-call start event, set session_state[session_id] = "in_tool" and record metadata (tool name, run id, start timestamp).
◦ When you detect a function completion/cancel/output event for that run id, set session_state[session_id] = "out_of_tool" and clear metadata.
1. Correlate events to runs
◦ Use identifiers available on the event (function call id, run id, or conversation item id) to ensure you only flip state when the matching run completes. This avoids race conditions when multiple tools are invoked.
1. Handle long-running tools
◦ If tools spawn async tasks, also watch for run-level interruption/cancellation flags exposed on the run context (for example, a speech_handle.interrupted or a cancellation token) to detect premature termination.
◦ Optionally track the task handle externally so you can query or cancel it if needed.
1. Expose the state for consumers
◦ Provide a small API or in-memory map (or use Redis / DB for multi-process setups) that other parts of your system can query: is_in_tool(session_id) → true/false, current_tool(session_id) → metadata.
If you tell me your SDK/language and whether tools are short- or long-lived, I’ll provide a short, targeted code sketch showing how to subscribe to events and flip the external state safely.acoustic-pharmacist-34178
03/06/2026, 6:34 PMtall-airline-16756
03/06/2026, 6:34 PMconversation_item_added (tool call start or tool output) and any explicit function-complete / function-error / cancellation events.
• When you detect a function-call start event, mark the session as in-tool and record the run id and tool name.
• When you detect the corresponding function completion / output / cancellation event for that run id, mark the session out-of-tool.
• For long-running tools, additionally track run cancellation/interruption flags if exposed on events or run metadata.
2) Minimal JS sketch (event-driven)
• Replace agentSession and event names with the actual JS SDK objects and event names you have (this sketch assumes the JS SDK emits conversation_item_added and that conversation item messages include type, run_id, and `role`/`content` or similar fields).
// sessionToolTracker.js
// In-memory map: sessionId -> { state: 'in_tool'|'out_of_tool', runId, toolName, startedAt }
const sessionState = new Map();
// Utility functions
function markInTool(sessionId, runId, toolName) {
sessionState.set(sessionId, {
state: 'in_tool',
runId,
toolName,
startedAt: Date.now(),
});
}
function markOutOfTool(sessionId, runId) {
const cur = sessionState.get(sessionId);
// Only clear if runId matches to avoid races
if (cur && cur.runId === runId) {
sessionState.set(sessionId, { state: 'out_of_tool' });
}
}
function isInTool(sessionId) {
const s = sessionState.get(sessionId);
return s && s.state === 'in_tool';
}
// Subscribe to session events
function attachSessionListeners(agentSession) {
const sessionId = agentSession.id; // adapt to actual SDK property
agentSession.on('conversation_item_added', (item) => {
// Example item shape assumptions:
// item.type === 'function_call' (start)
// item.type === 'function_result' (completion)
// item.run_id is present to correlate
// adapt to actual SDK event payload
if (item.type === 'function_call') {
// tool started
markInTool(sessionId, item.run_id, item.tool_name || item.name);
console.log(`[${sessionId}] tool started: ${item.tool_name} run=${item.run_id}`);
} else if (item.type === 'function_result' || item.type === 'function_error') {
// tool finished or errored
markOutOfTool(sessionId, item.run_id);
console.log(`[${sessionId}] tool finished: run=${item.run_id}`);
}
});
// Optional: listen for explicit cancellation/interrupt events if SDK emits them
agentSession.on('run_cancelled', (payload) => {
// payload.run_id
markOutOfTool(sessionId, payload.run_id);
console.log(`[${sessionId}] run cancelled: run=${payload.run_id}`);
});
// Clean up on session end
agentSession.on('session_finished', () => {
sessionState.delete(sessionId);
});
}
// Exported for other modules to query
module.exports = {
attachSessionListeners,
isInTool,
sessionState, // optional export for debugging or persistence
};
3) Notes and robustness tips
• Correlate by run id (or conversation item id) rather than just session id to avoid flipping state incorrectly when multiple tools are invoked in quick succession.
• If tools can be nested or concurrent, store a stack or counter per session instead of a single run id.
• For long-running/cancellable tools, listen for interruption or cancellation events (or check run_ctx.speech_handle.interrupted equivalent if surfaced via events) so you can mark out-of-tool when cancelled.
• Persist state to Redis or a DB when tracking across multiple processes or servers.
• Expose a small query API (e.g., isInTool(sessionId) or getCurrentTool(sessionId)) so other parts of your app can switch behavior based on context.
If you want, tell me:
• The exact event names and payload fields your JS SDK instance emits (I can adapt the sketch to match them).
• Whether tools can run concurrently or be nested (I’ll give a stack/counter variant).
I can then give a more exact code sample matching your SDK’s event shapes.