Skip to content

Vapi Squads

A squad is Vapi's primitive for multiple specialized agents that hand off to each other during a single phone call. Avoca's current-pattern clients (e.g., EAS Ponderosa via the StraightAway Auto v11 blueprint) use squads as the standard agent shape. This page explains what a squad is, how sub-agents differ from a single assistant, where the squad config lives, and the serverUrl priority hierarchy that determines where tool dispatches actually go.

Unsolicited Opinion

Most FDE confusion about "the agent" stems from treating it as one entity when it's actually 3-7 specialists collaborating. Once you internalize that, a lot of the architecture stops being weird — the priority-hierarchy footgun, blueprint-vs-Vapi-edit confusion, why prompt edits land in different surfaces depending on the conversational moment, all of it.

A squad is not a clone

The most common wrong mental model: "a squad is several copies of the same assistant running in parallel." It is not.

A squad is a team of specialists. Each sub-agent has:

  • Its own system prompt (specialized to one conversational task)
  • Its own tool list (different sub-agents call different tools)
  • Its own server.url — the webhook target for its tool dispatches
  • Its own first message (the line it speaks when control transfers to it)
  • Its own voice settings (usually shared across the squad, but configurable per-sub-agent)

Sub-agents hand off to each other based on the conversation. Only one sub-agent is "in control" at any given moment. The hand-off is itself a defined transition — from prompt A to prompt B, often triggered by intent classification.

Example squad shape (a typical auto-shop assistant)

Each box is a sub-agent with its own prompt + tools. The arrows are hand-off rules defined in the squad's hand-off graph.

Practical implication for FDEs: if you're changing how the agent handles cancel intent, you're editing the Cancel sub-agent's prompt, not "the assistant prompt." Find that sub-agent in the squad's multi_agent_config, edit its prompt section, save. Everything else stays untouched.

Where squad config lives

voice_assistants (Supabase)
  ↓ FK
assistant_configs (Supabase)
  .assistant_mode = 'MULTI_AGENT'
  .multi_agent_config = {
    "subAgents": [
      { "id": "greeter", "prompt": "...", "tools": [...], "server": { "url": "..." } },
      { "id": "booking", "prompt": "...", "tools": [...], "server": { "url": "..." } },
      ...
    ],
    "handoffs": [
      { "from": "greeter", "to": "booking", "condition": "..." },
      ...
    ]
  }

The squad's structure is JSON in assistant_configs.multi_agent_config. It's typically populated initially from a blueprint (a reusable squad template like StraightAway Auto v11). Editing the squad means mutating this JSON via Avoca admin, not Vapi's UI.

The serverUrl priority hierarchy (the footgun)

When Vapi processes a webhook destination during a call (a tool call, an event, an assistant-request), it walks three priority levels and uses the first one that's set. Higher priority shadows lower:

  1. Per-tool server.url (priority 1, highest) — set on each function tool's config inside the squad payload. Tool dispatches are routed by this level if it's set, regardless of priorities 2 and 3.
  2. Sub-agent assistant.server.url (priority 2) — set per-sub-agent in the squad payload. Used for assistant-level events (assistant-request, conversation-update, end-of-call-report) and as the fallback for any tool that lacks its own server.url.
  3. Phone-number server.url (priority 3, lowest) — set on the Vapi phone-number config. Used only when neither 1 nor 2 is set.

Two different surfaces, two different overrides

The classic FDE stuck-state: redirecting only the sub-agent's assistant.server.url (priority 2) and assuming tool dispatches will follow. They won't — Avoca's tools all carry priority-1 URLs, so tool dispatches go to those URLs regardless. Tool dispatches and assistant-level events are separate URL surfaces that need separate redirection.

For Avoca code specifically:

  • Every FunctionTool in lib/voice-assistants/tools/function-tool.ts builds its server.url via buildUrl(this.path). buildUrl() reads process.env.NGROK_BASE_URL and falls back to https://app.avoca.ai. So per-tool URLs (priority 1) are controlled by NGROK_BASE_URL at agent-build time.
  • The test-phone sync mechanism in lib/voice-assistants/test-phone/sync-test-phone.ts bakes a serverUrlOverride into every sub-agent's assistant.server.url. That URL comes from buildTestPhoneServerUrl, which reads RESPONDER_COMMON_WORKFLOW_URL. So sub-agent URLs (priority 2) are controlled by RESPONDER_COMMON_WORKFLOW_URL.

The sync-test-phone.ts comment captures the priority-2 piece:

"Bake the test server URL into every squad member's assistant.server.url (priority level 2 in VAPI's stack). The phone-level URL alone (priority 3) is silently shadowed if any sub-agent grows a serverUrl, so we set the higher-priority slot ourselves on this dedicated test squad."

Note the comment is silent on priority 1 — that level isn't rewritten by sync. It's set when AgentFactory builds the agent in-process, and FunctionTool.getVapiOpenAIModelTool calls buildUrl() then. That's why NGROK_BASE_URL has to be set in the dev server's environment before the sync, not after — the priority-1 URLs in the squad payload are frozen at build time.

How tool calls actually flow during a call

The diagram surfaces the two-surface separation: tool dispatches use priority 1 (per-tool server.url), assistant-level events use priority 2 (sub-agent assistant.server.url). Both have to be redirected for an FDE to fully exercise their branch locally — see Local Dev Setup for the env-var trio.

What gets edited where

ConcernSurface
Sub-agent promptAvoca admin / blueprint editor → mutates multi_agent_config.subAgents[i].prompt
Sub-agent tools (which tools the LLM can call)Avoca admin / blueprint editor → mutates multi_agent_config.subAgents[i].tools
Hand-off rules (when does Greeter hand to Booking)Avoca admin / blueprint editor → mutates multi_agent_config.handoffs
Sub-agent voice / TTSUsually shared squad-level setting; per-sub-agent override possible
Per-tool server.url (priority 1)Built by FunctionTool.getVapiOpenAIModelTool via buildUrl(path) at agent-build time. FDE controls via NGROK_BASE_URL env.
Sub-agent assistant.server.url (priority 2)Built by buildTestPhoneServerUrl at sync time. FDE controls via RESPONDER_COMMON_WORKFLOW_URL env (test-phone path only).
Phone number → squad mappingVapi dashboard (rare) OR Avoca admin (current pattern, dynamic)

Single-agent assistants for comparison

Not all Avoca-built voice assistants are squads. Some are single agents (assistant_mode = 'SINGLE_AGENT'). For those:

  • One system prompt, one tool list, no sub-agents
  • No hand-offs
  • The agent-factory builds a VapiAgent instead of MultiAgent
  • The priority-2 slot is the assistant's own server.url (no per-sub-agent layer); priority 1 (per-tool URLs) still applies the same way as in squads

The two-surface gotcha is identical for single agents: tool dispatches go to per-tool URLs (priority 1), assistant-level events to the assistant's server.url (priority 2). Same env-var trio applies for FDE local dev.

Cross-tenant blueprint reuse

A blueprint like StraightAway Auto v11 is a squad template. It defines the sub-agents, prompts, tools, and hand-off graph in a tenant-agnostic way (with {{ }} template variables for shop-specific values like the shop name, hours, services).

When a new auto-shop client onboards:

  1. Apply the StraightAway Auto v11 blueprint to a new assistant_configs row
  2. Substitute the per-tenant template variables (shop name, hours, integrations, etc.)
  3. Result: a working squad for the new client, designed once, instantiated N times

This is the multi-tenant scalability win of the current pattern. It only works because the squad's structure is data, not Vapi UI clicks.

Verified observations

We saw Blueprint: StraightAway Auto v11 in EAS's Vapi config, confirming this pattern is in use. The exact mechanism for blueprint edits propagating to existing tenants is still an open question (does an edit auto-roll out, or does each tenant need to re-apply?). See Unknowns.

  • Agent Architecture: Legacy vs Current — why squads are the strategic direction.
  • In-Call Sequence — the turn-by-turn loop, including how sub-agent hand-offs surface in the runtime.
  • Local Dev Setup — test-phone provisioning + sync, the FDE workflow that makes the priority hierarchy work in your favor.
  • Unknowns — the open architectural questions, including blueprint propagation behavior.