Appearance
In-Call Sequence
What happens during a single Vapi call, turn by turn — and where each piece of state lives. Use this to point to where a bug must be when behavior is wrong.
The loop, end-to-end
Where each piece of state actually lives
| Piece of state | Where it lives | How to inspect |
|---|---|---|
| System prompt | Vapi assistant config (Vapi dashboard) | Vapi dashboard → Assistants → click the EAS assistant → "Model" tab → "System Prompt" |
| Tool definitions (names, schemas, descriptions) | Vapi assistant config | Same place → "Tools" tab. Each tool has a name, description, JSON schema for arguments. |
| Tool URL (where Vapi POSTs each tool dispatch) | Per-tool server.url inside the squad payload (priority 1 in Vapi's resolution stack — see vapi-squads.md). Built at agent-build time by FunctionTool.getVapiOpenAIModelTool via buildUrl(this.path), which reads process.env.NGROK_BASE_URL (defaults to https://app.avoca.ai). | Vapi dashboard → squad detail → expand a sub-agent → each function tool's "Server URL" field. For FDE local dev, set NGROK_BASE_URL=<tunnel> then re-run test-phone-sync. |
| Tool handler (the code that runs) | Avoca's repo | apps/web/lib/vapi/tools/handlers/${toolName}.ts |
| Tool router (which handler fires for which tool name) | Avoca's repo | apps/web/pages/api/vapi/tools/dispatch.ts (or similar entry point) |
| Conversation history | Vapi runtime — exists for the duration of the call only | Vapi dashboard → Calls → click a call → "Transcript" tab shows the full turn-by-turn |
| Audio recording | Vapi (also forwarded to Avoca for storage) | Vapi dashboard call view; Avoca's recording_url on the calls row |
| Caller-id phone number | Vapi → forwards in webhook context | ctx.message.customer?.number in any tool handler |
"Here's a bug, where do I look?"
| Symptom | Most likely location |
|---|---|
| Agent uses the wrong opening line | System prompt (Vapi assistant config) |
| Agent doesn't call a tool when it should | Tool description / system prompt instructions about when to call the tool |
| Agent calls a tool with wrong arguments | Tool's JSON schema (arg shape) or system prompt's example/instruction for that tool |
| Tool call returns an error | Tool handler in Avoca's code (apps/web/lib/vapi/tools/handlers/${toolName}.ts) |
| Agent ignores tool's response | System prompt's instructions about how to use the result, OR tool result shape is too unstructured for the LLM to summarize |
| Agent hallucinates instead of using the tool result | Tool result's JSON is too noisy / the LLM is "explaining" instead of acting; tighten the tool's response shape AND add a prompt-level "always quote the X field" instruction |
| Agent loops calling the same tool repeatedly | Tool handler returned a result that doesn't satisfy the LLM's stop condition; OR system prompt is missing a "you've already done this, move on" guard |
| Tool fires but Avoca records nothing | Tool handler's persistence layer (Supabase write); or the tool returned synchronously but the side-effect was async without a guarantee |
| Agent says "let me look that up" then never references the result | Either the tool didn't actually fire (check Vapi transcript "tool calls" tab) or the prompt isn't telling the LLM how to use the response |
| Caller-id phone is wrong / missing | Vapi-side; sometimes customer.number is empty for inbound calls without caller ID. Defensive code in handlers. |
| Post-call workflow doesn't run | Avoca didn't receive /api/vapi/end-of-call-report, OR Inngest didn't pick up the event. Check Datadog for the webhook hit and Inngest dashboard for the run. |
| Post-call workflow ran but did nothing | Inngest run's step graph — extraction may have classified callReason differently than expected. |
How to "watch a call live"
In order of usefulness:
- Vapi dashboard, the call's page — refreshes the transcript in near-real-time. Each LLM turn shows up; each tool call shows up with its arguments + result expandable. This is the closest thing to a debugger for in-call behavior.
- Datadog logs filtered by
service:avoca-next-prod— every/api/vapi/tools/dispatchPOST shows up here as the call progresses. Filter further on@callIdonce you have it (Vapi puts the call id in the request body). - Tail the Avoca prod logs via Datadog MCP — once the MCP tool is wired (post-restart), you can ask in-session "show me recent /api/vapi/tools/dispatch hits in the last 5 minutes" and see them stream.
- Inngest dashboard (production) — only relevant after the call ends. The workflow run appears within seconds of the end-of-call webhook hitting.
Pointers
- The dispatch entry point in Avoca: search
apps/web/pages/api/vapi/tools/(Pages router) orapps/web/app/api/vapi/tools/(App router) — Avoca has both. - Tool handlers all live under
apps/web/lib/vapi/tools/handlers/. - The post-call entry point:
apps/web/app/api/vapi/end-of-call-report/(and its Inngest registration inapps/web/lib/workflow/post-call/end-of-call-report.ts). - Available tool catalog (the source of truth for tool name strings):
apps/web/lib/voice-assistants/available-tools.ts. - Per-tool URL builder:
apps/web/lib/voice-assistants/utils.ts—buildUrl(path)readsNGROK_BASE_URLand is what everyFunctionToolcalls when constructing itsserver.url.
Open in a new tab while debugging
- Vapi dashboard → the assistant being called → System Prompt tab
- Vapi dashboard → Calls → the live call (refresh)
- Datadog →
service:avoca-next-prod, last 30 min, refresh - Inngest → production env → Functions → EndOfCallReportInngestFunction (after call ends)