Skip to content

Vapi GitOps

Config-as-code management for the v3.0 voice agent squad

Last Updated: 2026-03-09 (v4.3.0 — audited against resolver.ts, state files, and README)


Overview

VitaraVox uses Vapi GitOps to manage the v3.0 squad configuration as code. All assistant prompts, tool definitions, and squad routing are defined in YAML/Markdown files and pushed to the Vapi API.

Directory: /home/ubuntu/vitara-platform/vapi-gitops/


Directory Structure

vapi-gitops/
├── .env.dev                                     # Dev Vapi token (VAPI_TOKEN, not VAPI_API_KEY)
├── .env.prod                                    # Prod Vapi token (separate account)
├── .vapi-state.dev.json                         # Dev slug → UUID mappings (auto-generated)
├── .vapi-state.prod.json                        # Prod slug → UUID mappings (auto-generated)
├── package.json                                 # 15 npm scripts
├── src/
│   ├── pull.ts                                  # Pull platform state
│   ├── push.ts                                  # Push local state to platform
│   ├── apply.ts                                 # Orchestrator: pull -> merge -> push
│   ├── call.ts                                  # WebSocket call script
│   ├── cleanup.ts                               # Cleanup orphaned resources
│   ├── types.ts                                 # TypeScript interfaces
│   ├── config.ts                                # Environment & configuration
│   ├── api.ts                                   # Vapi HTTP client
│   ├── state.ts                                 # State file management
│   ├── resources.ts                             # Resource loading (YAML, MD, TS)
│   ├── resolver.ts                              # Reference resolution
│   └── delete.ts                                # Deletion & orphan checks
├── resources/
│   ├── assistants/
│   │   ├── router-v3.md                         # Router prompt
│   │   ├── patient-id-en.md                     # Patient-ID English prompt
│   │   ├── patient-id-zh.md                     # Patient-ID Mandarin prompt
│   │   ├── booking-en.md                        # Booking English prompt
│   │   ├── booking-zh.md                        # Booking Mandarin prompt
│   │   ├── modification-en.md                   # Modification English prompt
│   │   ├── modification-zh.md                   # Modification Mandarin prompt
│   │   ├── registration-en.md                   # Registration English prompt
│   │   └── registration-zh.md                   # Registration Mandarin prompt
│   ├── tools/                                   # 14 tool YAML definitions
│   │   ├── search-patient-by-phone-8474536c.yml
│   │   ├── search-patient-4889f4e5.yml
│   │   ├── get-patient-d86dee47.yml
│   │   ├── get-clinic-info-aaec50cf.yml
│   │   ├── get-providers-1ffa2c33.yml
│   │   ├── find-earliest-appointment-7fc7534d.yml
│   │   ├── check-appointments-74246333.yml
│   │   ├── create-appointment-65213356.yml
│   │   ├── update-appointment-635f59ef.yml
│   │   ├── cancel-appointment-f6cef2e7.yml
│   │   ├── register-new-patient-9a888e09.yml
│   │   ├── add-to-waitlist-0153bac0.yml
│   │   ├── transfer-call-d95ed81e.yml
│   │   └── log-call-metadata-4619b3cb.yml
│   └── squads/
│       └── vitaravox-v3.yml                     # Squad routing configuration
└── simulations/                                 # Vapi simulation test resources
    ├── personalities/                           # Caller personality definitions (.yml)
    ├── scenarios/                               # Test scenario definitions (.yml)
    ├── tests/                                   # Individual simulation files (.yml)
    └── suites/                                  # Simulation suite files (.yml)

Configuration

Environment

The .env.dev file contains the Vapi API token:

VAPI_TOKEN=<your-vapi-api-token>

Variable Name

The environment variable is VAPI_TOKEN, not VAPI_API_KEY. Using the wrong name will cause silent authentication failures.

Variable Required Description
VAPI_TOKEN YES API authentication token
VAPI_BASE_URL no API base URL (defaults to https://api.vapi.ai)

State File

.vapi-state.dev.json maps local resource slugs to Vapi UUIDs. This file is auto-generated on first push and should not be manually edited.

Format:

{
  "assistants": {
    "router-v3": "4f70e214-6111-4f53-86c9-48f8f7c265e1",
    "patient-id-en": "7d054785-9074-4856-81db-9fe44da47bc5"
  },
  "tools": {
    "search-patient-by-phone-8474536c": "8474536c-663f-4a94-91ae-19e6221f9af9"
  },
  "squads": {
    "vitaravox-v3": "13fdfd19-a2cd-4ca4-8e14-ad2275095e32"
  },
  "structuredOutputs": {},
  "personalities": {},
  "scenarios": {},
  "simulations": {},
  "simulationSuites": {}
}

Commands

All commands are defined in package.json and use tsx to run TypeScript directly.

Command Description
npm run build Type-check the codebase (tsc --noEmit)
npm run push:dev Push local files to Vapi (dev)
npm run push:prod Push local files to Vapi (prod)
npm run push:dev:force Push with --force flag (dev)
npm run push:prod:force Push with --force flag (prod)
npm run pull:dev Pull platform state, preserve local changes (dev)
npm run pull:dev:force Pull platform state, overwrite everything (dev)
npm run pull:prod Pull from prod, preserve local changes
npm run pull:prod:force Pull from prod, overwrite everything
npm run apply:dev Pull -> Merge -> Push in one shot (dev)
npm run apply:prod Pull -> Merge -> Push in one shot (prod)
npm run call:dev Start a WebSocket call to an assistant/squad (dev)
npm run call:prod Start a WebSocket call to an assistant/squad (prod)
npm run cleanup:dev Clean up orphaned resources (dev)
npm run cleanup:prod Clean up orphaned resources (prod)

Selective Push

Push specific resource types or files:

# By resource type
npm run push:dev assistants
npm run push:dev tools
npm run push:dev squads

# By specific file
npm run push:dev resources/assistants/router-v3.md
npm run push:dev resources/tools/transfer-call-d95ed81e.yml

# Multiple files
npm run push:dev resources/assistants/booking-en.md resources/tools/create-appointment-65213356.yml

Note

Partial pushes skip deletion checks. Run full npm run push:dev to sync deletions.


Common Workflow

Push prompt changes

cd /home/ubuntu/vitara-platform/vapi-gitops

# Edit prompt markdown files in resources/assistants/
# Edit tool YAML files in resources/tools/ if needed

# Push all changes to Vapi dev environment
npm run push:dev

# Verify in Vapi dashboard or test with a phone call to +1 236-305-7446

Pull remote changes

# Preserve local changes, pull new platform resources
npm run pull:dev

# Force overwrite (platform becomes source of truth)
npm run pull:dev:force

# Review what changed
git diff

First-time setup

cd /home/ubuntu/vitara-platform/vapi-gitops
npm install

# Create .env.dev with your Vapi token
echo "VAPI_TOKEN=<your-token>" > .env.dev

# Pull all existing resources from Vapi
npm run pull:dev:force

# Commit initial state
git add . && git commit -m "initial pull"

Squad Architecture (v3.0)

Squad ID: 13fdfd19-a2cd-4ca4-8e14-ad2275095e32 Phone: +1 236-305-7446

Agent Slug UUID (short) STT TTS
Router router-v3 4f70e214 AssemblyAI Universal ElevenLabs
Patient-ID EN patient-id-en 7d054785 Deepgram nova-2 en ElevenLabs
Patient-ID ZH patient-id-zh 7585c092 Deepgram nova-2 zh Azure Xiaoxiao
Booking EN booking-en ac25775b Deepgram nova-2 en ElevenLabs
Booking ZH booking-zh 6ef04a40 Deepgram nova-2 zh Azure Xiaoxiao
Modification EN modification-en 9cd8381d Deepgram nova-2 en ElevenLabs
Modification ZH modification-zh e348cd2f Deepgram nova-2 zh Azure Xiaoxiao
Registration EN registration-en 9fcfd00d Deepgram nova-2 en ElevenLabs
Registration ZH registration-zh ce50df43 Deepgram nova-2 zh Azure Xiaoxiao

Tool request-start Configuration

3 of the 14 tools have audible request-start messages (from YAML source of truth):

Tool request-start Audible?
search_patient_by_phone "Let me pull up your file." Yes
find_earliest_appointment "Let me check what's available." Yes
check_appointments "Let me look that up." Yes
All 11 other tools "" No (silent)

This replaces the previous strategy of LLM-generated filler phrases, which caused timing issues. The FILLER PHRASE RULES sections were deleted from Booking and Modification prompts.


Prompt Engineering Notes

P0 Fixes Applied (v4.0.1)

  • Router maxTokens: Increased from 150 to 400 (GPT-4o tool-call JSON alone = 80-120 tokens)
  • Router greeting: Dynamic via get_clinic_info, not hardcoded "Vitara Clinic"
  • Patient-ID: firstMessage removed from squad members (was causing 16s silence)
  • All agents: transferAssistant references replaced with actual handoff_to_X function names
  • Booking/Modification: FILLER PHRASE RULES sections deleted (replaced by tool-level request-start)

Key Constraints

  • Tools are referenced by slug in assistant YAML frontmatter (e.g., find-earliest-appointment-7fc7534d), not by UUID
  • Squad YAML defines handoff routing between agents using tools:append with handoff tool type
  • Each agent's prompt must reference the correct handoff_to_X tool names
  • Router must have maxTokens >= 400 to avoid GPT-4o silent truncation
  • All tools use blocking: false in their request-start messages

File Format

  • Assistants: .md files with YAML frontmatter (config) and Markdown body (system prompt)
  • Tools: .yml files with Vapi function tool schema
  • Squads: .yml files defining member routing and handoff tools

Processing Order

Push (dependency order): Tools → Structured Outputs → Assistants → Squads → Personalities → Scenarios → Simulations → Simulation Suites

Delete (reverse): Simulation Suites → Simulations → Scenarios → Personalities → Squads → Assistants → Structured Outputs → Tools


Reference Resolution

The engine resolves human-readable slugs to Vapi UUIDs at push time. You write filenames; the API receives UUIDs.

Source: vapi-gitops/src/resolver.ts:157-303

WHAT YOU WRITE (YAML)                 WHAT THE API RECEIVES
─────────────────────                 ─────────────────────
toolIds:                              toolIds:
  - search-patient-by-phone-8474536c    - "8474536c-663f-4a94-91ae-..."

members:                              members:
  - assistantId: router-v3              - assistantId: "4f70e214-6111-..."
    assistantDestinations:                assistantDestinations:
      - assistantId: patient-id-en          - assistantId: "7d054785-9074-..."

Resolution Rules

Rule Behavior Source
Slug → UUID Look up slug in .vapi-state.{env}.json, return UUID resolver.ts:26-31
Already a UUID Pass through unchanged (regex match ^[0-9a-f]{8}-...) resolver.ts:22-24
YAML comments Strip ## Comment suffix before lookup (id.split("##")[0]) resolver.ts:19
Not found Emit ⚠️ Reference not found warning, skip the ID resolver.ts:28-29

Resolved Reference Points

The resolver walks the full resource tree. These are all the locations where slug → UUID resolution occurs:

Location Field Applies To
model.toolIds[] Tool slugs → UUIDs Assistants
artifactPlan.structuredOutputIds[] Structured output slugs Assistants
members[].assistantId Assistant slugs Squads
members[].assistantDestinations[].assistantId Handoff targets Squads
destinations[].assistantId Transfer targets Handoff tools
hooks[].do[].toolId Hook action tools Assistants
personalityId Personality slug Simulations
scenarioId Scenario slug Simulations
simulationIds[] Simulation slugs Simulation Suites
evaluations[].structuredOutputId Eval output slugs Scenarios

Dev vs Prod Environments

The engine supports multiple environments through separate state files and env files. Same resource YAML, different Vapi accounts.

                     SAME YAML FILES
                     ┌──────────────┐
                     │  resources/  │
                     │  assistants/ │
                     │  tools/      │
                     │  squads/     │
                     └──────┬───────┘
               ┌────────────┴────────────┐
               │                         │
        push:dev                  push:prod
               │                         │
     .env.dev (token A)        .env.prod (token B)
               │                         │
     .vapi-state.dev.json      .vapi-state.prod.json
     (dev UUIDs)               (prod UUIDs)
               │                         │
        Vapi Dev Account         Vapi Prod Account
Aspect Dev Prod
Env file .env.dev .env.prod
State file .vapi-state.dev.json .vapi-state.prod.json
Push command npm run push:dev npm run push:prod
Pull command npm run pull:dev npm run pull:prod
Force flag npm run push:dev:force npm run push:prod:force
Apply npm run apply:dev npm run apply:prod
Call test npm run call:dev npm run call:prod

State Files Are Environment-Specific

The same slug (e.g., router-v3) maps to different UUIDs in dev vs prod. Never copy state files between environments — push creates them automatically.

Current State (Dev)

As of v4.3.0, the dev state file tracks:

Resource Type Count Examples
Assistants 9 router-v34f70e214, booking-enac25775b
Tools 14 search-patient-by-phone-8474536c8474536c
Squads 1 vitaravox-v313fdfd19

Simulation Framework

Vapi simulations enable automated testing of voice agent flows. Resources are organized in simulations/ subdirectories.

Resource Directory Purpose
Personality simulations/personalities/ Define caller personas (e.g., "impatient patient", "Mandarin speaker")
Scenario simulations/scenarios/ Define test scripts (e.g., "book appointment happy path")
Simulation simulations/tests/ Combine personality + scenario into a test case
Suite simulations/suites/ Group simulations into test runs

File Formats

Personality:

name: Impatient Patient
description: Caller in a hurry, gives short answers
prompt: You are impatient and want to book quickly. Give minimal information.

Scenario:

name: Happy Path - Booking
description: New patient books a checkup
prompt: |
  You are calling to book a general checkup.
  Your name is John Smith, phone 604-555-1234.
evaluations:
  - structuredOutputId: booking-eval  # resolved to UUID at push

Simulation:

name: Booking Test 1
personalityId: impatient-patient       # slug, resolved to UUID
scenarioId: happy-path-booking         # slug, resolved to UUID

Suite:

name: Booking Flow Tests
simulationIds:
  - booking-test-1                     # slug, resolved to UUID
  - booking-test-2

Status

The simulation directories are currently empty. Population is planned for the v3.0 E5 Testing milestone (76 test cases).


Troubleshooting

Symptom Cause Fix
"Reference not found" Resource file missing or misnamed Check filename matches exactly (case-sensitive), no extension in reference
"Cannot delete resource" Still referenced by another resource Remove references first, then push, then delete file
Resource not updating Stale UUID in state file Delete entry from .vapi-state.dev.json, re-push
Silent auth failure Wrong env var name Use VAPI_TOKEN, not VAPI_API_KEY
API "property X should not exist" Immutable field in update Add to UPDATE_EXCLUDED_KEYS in src/config.ts