> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.jambonz.org/guides/features/voice-agents/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.jambonz.org/_mcp/server. # Voice Agents The [agent](/verbs/verbs/agent) verb orchestrates a complete voice AI agent by wiring together three separate components — STT, LLM, and TTS — with integrated turn detection. Unlike the [llm](/verbs/verbs/llm) verb (which connects to speech-to-speech APIs where a single vendor handles everything), the agent verb lets you mix and match: for example, Deepgram for STT, Anthropic for the LLM, and Cartesia for TTS. The agent verb manages the full conversational turn cycle: 1. User speaks → STT produces a transcript 2. Turn detection decides the user is done speaking 3. Transcript is sent to the LLM 4. LLM response tokens stream to TTS 5. TTS audio plays back to the caller 6. If the user barges in, TTS stops and a new turn begins > **Tip** > > **Looking for runnable examples?** The [jambonz/v10-examples](https://github.com/jambonz/v10-examples/tree/main/examples/agent) repository has working demos for every feature described in this guide — basic usage, tool calling, MCP servers, CRM injection, persona switching, supervisor overrides, and more. Clone it and run any example end-to-end in minutes. ## Basic Setup The `llm` property is the only required field. STT and TTS will use your application's default speech credentials if not specified. Below is a minimal voice agent using the [Node.js SDK](/sdks/node-sdk) and the application defaults for STT and TTS. ```javascript const http = require('node:http'); const { createEndpoint } = require('@jambonz/sdk/websocket'); const envVars = { OPENAI_MODEL: { type: 'string', description: 'OpenAI model to use', default: 'gpt-4.1-mini', }, SYSTEM_PROMPT: { type: 'string', description: 'System prompt for the voice agent', uiHint: 'textarea', default: [ 'You are a helpful voice AI assistant.', 'The user is interacting with you via voice,', 'even if you perceive the conversation as text.', 'You eagerly assist users with their questions', 'by providing information from your extensive knowledge.', 'Your responses are concise, to the point,', 'and use natural spoken English with proper punctuation.', 'Never use markdown, bullet points, numbered lists,', 'emojis, asterisks, or any special formatting.', 'You are curious, friendly, and have a sense of humor.', 'When the conversation begins,', 'greet the user in a helpful and friendly manner.', ].join(' '), }, }; const port = parseInt(process.env.PORT || '3000', 10); const server = http.createServer(); const makeService = createEndpoint({ server, port, envVars }); const svc = makeService({ path: '/' }); svc.on('session:new', (session) => { console.log('session:new received', JSON.stringify({ call_sid: session.data.call_sid, direction: session.data.direction, from: session.data.from, to: session.data.to, env_vars: session.data.env_vars, }, null, 2)); try { const model = session.data.env_vars?.OPENAI_MODEL || envVars.OPENAI_MODEL.default; const systemPrompt = session.data.env_vars?.SYSTEM_PROMPT || envVars.SYSTEM_PROMPT.default; console.log('using model:', model); session.on('/agent-event', (evt) => { console.log('agent-event received:', evt.type); if (evt.type === 'turn_end') { const { transcript, response, interrupted, latency } = evt; console.log('turn_end', JSON.stringify({ transcript, response, interrupted, latency }, null, 2)); } }); session.on('/agent-complete', () => { console.log('agent-complete received, sending hangup'); session.hangup().reply(); }); console.log('sending agent verb...'); session .agent({ llm: { vendor: 'openai', model, llmOptions: { messages: [{ role: 'system', content: systemPrompt }], }, }, turnDetection: 'krisp', earlyGeneration: true, bargeIn: { enable: true }, eventHook: '/agent-event', actionHook: '/agent-complete', }) .send(); console.log('agent verb sent'); } catch (err) { console.error('Error in session:new handler:', err); } }); svc.on('error', (err) => { console.error('service error:', err); }); console.log(`voice agent listening on port ${port}`); ``` ### Supported LLM Vendors The jambonz portal lets you "bring your own LLM" in a similar fashion to speech credentials. Configure credentials in **Account → LLM Services**, then reference the vendor in the agent verb. 11 LLM vendors are supported: Anthropic, AWS Bedrock, Azure OpenAI, Baseten, DeepSeek, Google AI Studio, Groq, HuggingFace, OpenAI, and Vertex AI (Gemini and Partner Models). See [Bring Your Own LLM](/guides/features/bring-your-own-llm) for per-vendor setup, model recommendations, and known issues, or the [agent verb reference](/verbs/verbs/agent#supported-llm-vendors) for the full vendor id + example-models table. The agent verb normalizes message formats and tool schemas across vendors automatically. You write tools in OpenAI format and the agent verb adapts them for each vendor. ### Authentication By default, the agent verb uses speech credentials configured in the jambonz portal. You can also pass credentials directly: ```js llm: { vendor: 'openai', model: 'gpt-4.1-mini', auth: { apiKey: process.env.OPENAI_API_KEY }, // ... } ``` For AWS Bedrock, pass `accessKeyId`, `secretAccessKey`, and `region` in the `auth` object. ## Turn Detection The `turnDetection` property controls how the agent verb decides the user has finished speaking. We currently support only two modes — STT-based detection and [Krisp's turn detection model](https://lab.krisp.ai/products/turn-taking). > **Self-Hosted Licensing** > > Krisp turn detection requires a separate Krisp API license on self-hosted deployments. See [Krisp configuration](/self-hosting/krisp) for details. This does not apply to jambonz.cloud customers. ### STT-based detection (default) ```json { "turnDetection": "stt" } ``` Uses the STT vendor's native end-of-utterance signal. For most vendors this is silence-based. Some vendors have smarter built-in turn detection: * **deepgramflux** — Acoustic + semantic turn detection (Deepgram's "Flux" model) * **assemblyai** — Native turn-taking with the `u3-rt-pro` model * **speechmatics** — Built-in turn detection These vendors always use their native detection regardless of the `turnDetection` setting. ### Krisp turn detection ```json { "turnDetection": { "mode": "krisp", "threshold": 0.5 } } ``` Uses the Krisp acoustic end-of-turn model, which analyzes speech patterns rather than just silence. Good for natural conversation where users pause mid-thought. * `threshold` — Confidence threshold from 0.0 to 1.0. Lower values trigger earlier turn transitions (more aggressive). Default: 0.5. * `model` — Optional Krisp model name override. The shorthand `"turnDetection": "krisp"` uses default settings. > **Krisp Licensing for Self-Hosted Systems** > > Krisp turn detection requires a Krisp API license key on self-hosted jambonz deployments. This license is **not included** with the jambonz software license and must be obtained separately from Krisp. > > Contact [support@jambonz.org](mailto:support@jambonz.org) for information on obtaining a Krisp license for your self-hosted deployment. > > This requirement does not apply to jambonz.cloud customers — Krisp features are included with all jambonz.cloud plans. ## Early Generation (Speculative Preflight) Early generation speculatively sends the transcript to the LLM *before* end-of-turn is confirmed. If the transcript matches when the turn does end, buffered tokens are released immediately — shaving off the LLM prompt time. If the user keeps talking and the transcript changes, the speculative response is discarded. There are two ways early generation is triggered: * **Krisp turn detection** — Set `earlyGeneration: true` to opt in. Krisp emits an early signal that triggers the speculative LLM prompt before final end-of-turn confirmation. * **Deepgram Flux** — Early generation happens automatically. Flux emits a native `EagerEndOfTurn` event that triggers preflight regardless of the `earlyGeneration` setting. For other STT vendors with native turn-taking (assemblyai, speechmatics), early generation is not available. ```js session.agent({ turnDetection: 'krisp', earlyGeneration: true, // ... }).send(); ``` The [turn\_end event](#event-handling) includes `preflight` metrics so you can track hit rates: * `hit` — speculative transcript matched final, tokens released immediately * `miss` — transcript changed, speculative response discarded * `pending` — preflight was still in progress when the turn ended ## Barge-in Configuration By default, users can interrupt the assistant while it's speaking. The `bargeIn` object controls this behavior: ```json { "bargeIn": { "enable": true, "minSpeechDuration": 0.5, "sticky": false } } ``` * `enable` — Allow interruptions. Default: `true`. * `minSpeechDuration` — Seconds of speech required to confirm an interruption. Prevents brief noises (coughs, background sounds) from cutting off the assistant. Default: 0.5. * `sticky` — If `true`, once the user interrupts, the assistant does not resume speaking the interrupted response. Default: `false`. **Tuning tips:** * Lower `minSpeechDuration` (e.g., 0.2) for more responsive barge-in * Higher `minSpeechDuration` (e.g., 1.0) for noisy environments where false triggers are common * Set `enable: false` for scenarios where the assistant must complete its message (e.g., legal disclaimers) ## No Response Timeout The `noResponseTimeout` property handles the case where the user goes silent after the assistant finishes speaking. ```json { "noResponseTimeout": 12 } ``` When the timeout fires, the LLM is prompted with a system cue: *"The user has not responded. Briefly check if they are still there or ask if they need help."* This generates a natural follow-up rather than leaving dead air. Defaults to `12` seconds. Set to `0` to disable. The timer is cancelled if the user starts speaking. This also covers the "missed speech" case: when VAD detects speech but STT returns no transcript, the no-response timer handles the re-prompt. ## Greeting By default (`greeting: true`), the agent verb prompts the LLM to generate an initial greeting before the user speaks. Set `greeting: false` if you want the agent to wait silently for the user to speak first. ## Tool/Function Calling The agent verb supports LLM tool/function calling, allowing your agent to perform actions like looking up data, calling APIs, or transferring calls. There are two ways to provide tools: 1. **Roll your own** — define a JSON schema, list it in `llmOptions.tools`, and handle the tool call yourself in a `toolHook` handler. Use this for tools specific to your application (CRM lookups, business logic, proprietary APIs). 2. **Use pre-built tools from [`@jambonz/tools`](https://github.com/jambonz/tools)** — drop in ready-made tools (web search, weather, Wikipedia, calculator, datetime) without writing schemas or handlers. Use this for common utility tools. You can mix both approaches in the same agent — they share the same `toolHook` path. ### Rolling your own tools Use this approach for tools that are specific to your application. You supply the schema and handle the execution yourself. #### Defining the tool schema Define tools in `llm.llmOptions.tools` using the standard function-calling format: ```typescript const weatherTool = { name: 'get_weather', description: 'Get the current temperature and wind speed for a location.', parameters: { type: 'object', properties: { location: { type: 'string', description: 'City name, e.g. "Portland"' }, scale: { type: 'string', enum: ['celsius', 'fahrenheit'] }, }, required: ['location'], }, }; session.agent({ llm: { vendor: 'openai', model: 'gpt-4.1-mini', llmOptions: { messages: [{ role: 'system', content: 'You are a weather assistant.' }], tools: [weatherTool], }, }, toolHook: '/tool-call', // ... }).send(); ``` The agent verb normalizes tool schemas across LLM vendors. You always define tools in the same format regardless of whether you're using OpenAI, Anthropic, Google, or Bedrock. #### Handling tool calls (WebSocket) Tool calls arrive as events on the `toolHook` path with `tool_call_id`, `name`, and `arguments` (already parsed as an object). Respond with `session.sendToolOutput()`: ```typescript session.on('/tool-call', async (evt) => { const { tool_call_id, name, arguments: args } = evt; if (name === 'get_weather') { try { const geoRes = await fetch( `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(args.location)}&count=1` ); const geoData = await geoRes.json(); const { latitude, longitude } = geoData.results[0]; const wxRes = await fetch( `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,wind_speed_10m` ); const weather = await wxRes.json(); session.sendToolOutput(tool_call_id, `Temperature: ${weather.current.temperature_2m}°C, Wind: ${weather.current.wind_speed_10m} km/h` ); } catch (err) { session.sendToolOutput(tool_call_id, `Error: ${err.message}`); } return; } session.sendToolOutput(tool_call_id, `Unknown tool: ${name}`); }); ``` #### Handling tool calls (Webhook) In webhook mode, the tool call arrives as an HTTP POST to the `toolHook` URL. Return the tool result as JSON in the response body. ### Using pre-built tools from `@jambonz/tools` For common utility tools — web search, weather, Wikipedia, calculator, datetime — the [`@jambonz/tools`](https://github.com/jambonz/tools) package lets you skip the schema definition and handler code entirely. Each tool bundles a JSON Schema (for the LLM) and an `execute()` function (for your application) that are wired into your session with a single call. > **Note** > > `@jambonz/tools` is open source (MIT-licensed) and we actively welcome community contributions. If you've built a useful tool — a CRM lookup, a scheduling integration, a knowledge-base query — please consider [opening a PR](https://github.com/jambonz/tools) so other jambonz developers can use it. See the contributing guidelines in the repo README. ```bash npm install @jambonz/tools ``` Available tools: | Tool | Factory | API key | Description | | ----------- | -------------------- | ------- | --------------------------------------------- | | Web Search | `createTavilySearch` | Tavily | Search the web for current info | | Weather | `createWeather` | none | Current weather for any location (Open-Meteo) | | Wikipedia | `createWikipedia` | none | Factual summaries | | Calculator | `createCalculator` | none | Safe math expression evaluator | | Date & Time | `createDateTime` | none | Current date/time for any timezone | `registerTools()` wires the tools into your session — it listens on the `toolHook` path, dispatches each incoming tool call to the matching `execute()` function, and sends the result back via `sendToolOutput()`: ```typescript import { createTavilySearch, createWeather, createCalculator, registerTools } from '@jambonz/tools'; const search = createTavilySearch({ apiKey: process.env.TAVILY_API_KEY }); const weather = createWeather({ scale: 'fahrenheit' }); const calc = createCalculator(); const tools = [search, weather, calc]; svc.on('session:new', (session) => { registerTools(session, '/tool-call', tools); session.agent({ stt: { vendor: 'deepgram', language: 'multi' }, tts: { vendor: 'cartesia', voice: '9626c31c-bec5-4cca-baa8-f8ba9e84c8bc' }, llm: { vendor: 'openai', model: 'gpt-4.1-mini', llmOptions: { messages: [{ role: 'system', content: 'You are a helpful voice assistant with web search, weather, and math tools. ' + 'Keep responses concise and conversational.', }], tools: tools.map((t) => t.schema), }, }, toolHook: '/tool-call', actionHook: '/agent-complete', }).send(); }); ``` `registerTools()` also accepts a `logger` option and returns errors to the LLM if a tool throws or is called with an unknown name. ### Combining both approaches You can mix pre-built tools from `@jambonz/tools` with your own custom tools in the same agent. Include the schemas from both in `llmOptions.tools`, use `registerTools()` for the pre-built ones, and attach your own `toolHook` handler for the custom ones. The two dispatch paths run side by side — `registerTools()` only handles tool calls whose `name` matches one it was given, so custom calls fall through to your handler. ```typescript const myTool = { name: 'lookup_order', description: 'Look up an order by ID', parameters: { type: 'object', properties: { order_id: { type: 'string' } }, required: ['order_id'], }, }; // pre-built tools registerTools(session, '/tool-call', [search, weather]); // custom tool handler — runs alongside registerTools session.on('/tool-call', async (evt) => { if (evt.name === 'lookup_order') { const order = await db.orders.find(evt.arguments.order_id); session.sendToolOutput(evt.tool_call_id, JSON.stringify(order)); } }); session.agent({ llm: { vendor: 'openai', model: 'gpt-4.1-mini', llmOptions: { tools: [search.schema, weather.schema, myTool], }, }, toolHook: '/tool-call', // ... }).send(); ``` You can also inject pre-built tools mid-conversation using [`updateAgent`](#update_tools): ```typescript session.updateAgent({ type: 'update_tools', tools: [search.schema, weather.schema], }); ``` ## MCP Server Integration Instead of (or in addition to) defining tools inline, you can connect to external [MCP](https://modelcontextprotocol.io/) servers. The agent verb connects to each server at startup via SSE or Streamable HTTP transport, discovers available tools, and makes them callable by the LLM. ```typescript session .agent({ llm: { vendor: 'openai', model: 'gpt-4.1', llmOptions: { messages: [{ role: 'system', content: 'You are a sports assistant. Use available tools to answer questions about live scores.', }], }, }, stt: { vendor: 'deepgram', language: 'en-US' }, tts: { vendor: 'cartesia', voice: 'sonic-english' }, mcpServers: [ { url: 'https://livescoremcp.com/sse' }, ], actionHook: '/agent-complete', }) .send(); ``` A caller can simply ask "what football matches are on right now?" and the LLM will use the tools discovered from the MCP server to fetch real-time data — no need to define tool schemas in `llmOptions.tools`. If an MCP server requires authentication: ```json { "mcpServers": [ { "url": "https://mcp.tavily.com/mcp/?tavilyApiKey=your-key", "auth": { "apiKey": "your-key" } } ] } ``` **Tool dispatch priority**: When the LLM requests a tool call, MCP servers are checked first. If the tool name matches one discovered from an MCP server, the call is dispatched there. Otherwise, it falls through to the `toolHook` webhook. You can use both together. ## Mid-conversation Updates The agent verb supports asynchronous updates while a conversation is in progress, allowing you to change the agent's behavior, inject context, modify tools, or trigger responses — without interrupting the verb stack. Updates are sent via WebSocket (`session.updateAgent(data)`) or REST API. ### update\_instructions Replace the LLM system prompt mid-conversation. Useful for persona switching or topic transitions. ```typescript // After identifying the caller's intent, switch to a specialist persona session.updateAgent({ type: 'update_instructions', instructions: 'You are now a billing support agent. Help the caller with invoice questions.', }); ``` ### inject\_context Append messages to the LLM conversation history. System messages are routed to the system prompt for vendors that don't support inline system messages (Bedrock, Anthropic, Google). ```typescript // Inject CRM data after identifying the caller session.updateAgent({ type: 'inject_context', messages: [ { role: 'user', content: 'CRM context: Customer name: Sarah Mitchell. Account tier: Gold. ' + 'Open support ticket: delayed delivery on the smart home hub.', }, ], }); ``` ### update\_tools Replace the tool set available to the LLM. The new tools take effect on the next turn. ```typescript // Add web search capability after the user requests it session.updateAgent({ type: 'update_tools', tools: [ { name: 'web_search', description: 'Search the web for current information', parameters: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'], }, }, ], }); ``` ### generate\_reply Prompt the LLM to generate a new response. If the agent verb is idle, the prompt executes immediately. If busy, the request is queued. Use `interrupt: true` to cancel the current response and generate immediately — useful for supervisor overrides or urgent notifications. ```typescript // Supervisor whisper — interrupt with urgent info session.updateAgent({ type: 'generate_reply', interrupt: true, user_input: 'URGENT: Tell the customer about the flash sale — 50% off all items for the next hour.', }); // Gentle prompt with one-shot instructions session.updateAgent({ type: 'generate_reply', user_input: 'Customer is asking about refunds', instructions: 'Be empathetic and offer a 20% discount before processing a refund.', }); ``` ## Event Handling The `eventHook` receives real-time events during the conversation. In WebSocket mode, listen with `session.on()`: ```typescript session.on('/agent-event', (evt) => { switch (evt.type) { case 'user_transcript': console.log('User said:', evt.transcript); break; case 'agent_response': console.log('Agent replied:', evt.response); break; case 'user_interruption': console.log('User interrupted'); break; case 'turn_end': console.log('Turn complete:', { transcript: evt.transcript, response: evt.response, latency: evt.latency, }); break; } }); ``` ### Event Types | Event | Description | Key fields | | -------------------- | ----------------------- | ------------------------------------------------------------------------------ | | `user_transcript` | User speech recognized | `transcript` | | `agent_response` | Assistant reply text | `response` | | `user_interruption` | User barged in | — | | `turn_end` | End-of-turn summary | `transcript`, `confidence`, `response`, `interrupted`, `latency`, `tool_calls` | | `history_summarized` | Conversation summarized | `turn`, `messages_dropped`, `messages_kept`, `summary` | ### turn\_end Payload The `turn_end` event is the most useful for observability. Example payload: ```json { "type": "turn_end", "transcript": "What's the weather in Portland?", "confidence": 0.998, "response": "The temperature in Portland is 52°F with wind at 12 km/h.", "interrupted": false, "latency": { "stt_ms": 320, "eot_ms": 180, "llm_ms": 890, "tool_ms": 420, "tts_ms": 210, "preflight": { "result": "hit", "tokens": 12 } }, "tool_calls": [ { "name": "get_weather", "rtt_ms": 420 } ] } ``` ## Latency Optimization The `turn_end` latency breakdown helps you identify bottlenecks and optimize response time. | Field | What it measures | How to optimize | | ----------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `stt_ms` | STT processing time | Choose low-latency STT vendors (Deepgram). Use `hints` to improve accuracy. | | `eot_ms` | End-of-turn detection wait | Tune Krisp `threshold` (lower = faster). Use vendors with native turn-taking. | | `llm_ms` | Pure LLM thinking time (tool RTT subtracted) | Use faster models (e.g., `gpt-4.1-mini`). Keep system prompts concise. Enable `earlyGeneration`. | | `tool_ms` | Total time in tool calls | Optimize tool endpoint latency. Use caching where appropriate. | | `tts_ms` | TTS engine latency (text → first audio) | Choose streaming-capable TTS (Cartesia, ElevenLabs, Deepgram). | | `preflight` | Speculative preflight result | Enable `earlyGeneration` with Krisp. Monitor hit rate — high miss rates may indicate the threshold is too aggressive. | ## Conversation History Summarization For long conversations that might exceed the LLM's context window, the agent verb can automatically summarize older turns. Set the `JAMBONES_PIPELINE_SUMMARIZE_TURNS` environment variable to control how often summarization runs. Values 1–7 are clamped to 8. Set to `0` to disable (default). When summarization triggers: 1. The LLM generates a concise summary of the older conversation turns 2. The summary is appended to the system prompt as a "Conversation context" section 3. The summarized turns are dropped from conversation history 4. Half the configured number of turns are kept in full fidelity A `history_summarized` event is sent to the `eventHook`: ```json { "type": "history_summarized", "turn": 8, "messages_dropped": 5, "messages_kept": 6, "summary": "The user is a software developer looking for a MacBook Pro..." } ``` ## Noise Isolation The `noiseIsolation` property enables server-side noise cancellation on call audio, improving STT accuracy in noisy environments. Two vendors are available: * **`"krisp"`** — Krisp's proprietary noise cancellation. [Listen to audio samples](https://lab.krisp.ai/products/voice-isolation/audio-samples) to hear the model in action. * **`"rnnoise"`** — Open-source RNNoise-based noise cancellation. No API key required. > **Note** > > Krisp noise isolation has the same licensing requirement as Krisp turn detection — see the [warning above](#krisp-turn-detection) for self-hosted licensing details. Shorthand: ```json { "noiseIsolation": "krisp" } ``` Detailed configuration: ```json { "noiseIsolation": { "mode": "krisp", "level": 80, "direction": "read" } } ``` * `level` — Suppression level 0–100. Higher values are more aggressive. Default: 100. * `direction` — `"read"` filters caller audio (default), `"write"` filters outbound audio. ## Error Recovery The agent verb handles errors gracefully to keep the conversation going: * **LLM errors with tools** — If the LLM fails and tools were included, the agent verb retries the same prompt without tools. This handles models that don't support tool use in certain configurations. * **Speculative preflight errors** — When a speculative prompt fails, the preflight is discarded and a fresh prompt is issued normally. * **Recovery to idle** — On unrecoverable LLM errors, the agent verb ends the turn and transitions to idle so the user can continue speaking. The no-response timer is not started after an error to avoid retry loops. * **STT reconnection** — The agent verb automatically reconnects the STT stream if the connection drops. When the agent verb encounters an unrecoverable error, it invokes the `actionHook` with a `completion_reason` indicating the failure. ## Example Applications The [agent examples](https://github.com/jambonz/v10-examples/tree/main/examples/agent) repository contains runnable demos for each feature: | Example | What it demonstrates | | ------------------------- | ------------------------------------------------------------ | | `deepgram-cartesia` | Basic agent with Deepgram STT + Cartesia TTS | | `deepgramflux-elevenlabs` | Deepgram Flux (native turn detection) + ElevenLabs TTS | | `speechmatics-rime` | Speechmatics STT + Rime TTS | | `using-tools` | Tool calling with weather lookup | | `web-search` | Web search via Tavily tool | | `using-mcp-server` | MCP server for live sports scores | | `tavily-mcp` | Web search via Tavily MCP server | | `crm-injection` | Live CRM context injection via `inject_context` | | `persona-switch` | Mid-conversation persona change via `update_instructions` | | `supervisor-interrupt` | Urgent message injection via `generate_reply` with interrupt | | `dynamic-tools` | Mid-conversation tool injection via `update_tools` | > Build conversational AI agents using the [agent](/verbs/verbs/agent) verb.