Agent Studio

Data Model

Entity relationships and database schema

Data Model

Agent Studio uses PostgreSQL with JSONB columns for flexible configuration storage.

Entity Relationship Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│                              TENANT                                          │
│  (Organization - top-level isolation boundary)                              │
└─────────────────────────────────────────────────────────────────────────────┘

         │ 1:N

┌────────┴────────┬─────────────────┬─────────────────┬─────────────────┬─────────────────┐
│                 │                 │                 │                 │                 │
▼                 ▼                 ▼                 ▼                 ▼                 ▼
┌─────────┐  ┌─────────┐  ┌─────────────┐  ┌─────────┐  ┌──────────────┐  ┌─────────┐
│ AGENTS  │  │WORKFLOWS│  │PROVIDER_KEYS│  │  TOOLS  │  │   API_KEYS   │  │  USERS  │
└─────────┘  └─────────┘  └─────────────┘  └─────────┘  └──────────────┘  └─────────┘
     │            │                                           
     │            │ N:M (workflow_agents junction)            
     └────────────┴───────────────────────────────────────────

                  │ 1:N

            ┌──────────┐
            │  CALLS   │
            └──────────┘

Tenant

The top-level isolation boundary. All data belongs to a tenant.

class Tenant(Base):
    __tablename__ = "tenants"
    
    id: Mapped[UUID]           # Primary key
    slug: Mapped[str]          # URL-friendly identifier (unique)
    name: Mapped[str]          # Display name
    plan: Mapped[TenantPlan]   # FREE, STARTER, PRO, ENTERPRISE
    settings: Mapped[dict]     # JSONB configuration
    is_active: Mapped[bool]

Settings JSONB Schema

{
  "webhook_url": "https://api.example.com/webhooks",
  "default_language": "en",
  "supported_languages": ["en", "hi", "ta"],
  "feature_flags": {
    "async_feedback": true,
    "cgm_support": false
  },
  "api_config": {
    "base_url": "https://...",
    "timeout": 30,
    "retry_count": 2
  },
  "endpoints": {
    "meal.log": { "path": "/api/meals", "method": "POST" },
    "user.profile": { "path": "/api/users/{user_id}", "method": "GET" }
  }
}

Agent

Voice AI agent configuration.

class Agent(Base):
    __tablename__ = "agents"
    
    id: Mapped[UUID]
    tenant_id: Mapped[UUID]     # FK to tenants
    name: Mapped[str]           # Unique per tenant
    display_name: Mapped[str]
    config: Mapped[dict]        # JSONB configuration
    is_active: Mapped[bool]

Config JSONB Schema

{
  "prompt": {
    "system": "You are a health coach...",
    "greeting": "Hello {{user_name}}!",
    "language_variants": { "hi": "आप एक स्वास्थ्य कोच हैं..." },
    "variables": [{ "name": "user_name", "default": "there" }]
  },
  "stt": { "provider": "deepgram", "model": "nova-2" },
  "tts": { 
    "provider": "cartesia", 
    "voice_id": "...", 
    "voices": { "en": "...", "hi": "..." }
  },
  "llm": { 
    "provider": "gemini", 
    "model": "gemini-2.0-flash-exp", 
    "temperature": 0.7 
  },
  "vad": { "provider": "silero", "activation_threshold": 0.5 },
  "tools": ["save_meal", "log_glucose", "transfer_to_next"],
  "handoffs": [
    { "target_agent": "glucose-agent", "conditions": [...] }
  ],
  "session": { 
    "max_tool_steps": 10, 
    "auto_disconnect_timeout": 600 
  },
  "languages": ["en", "hi"],
  "default_language": "en"
}

Workflow

Multi-agent workflow definition.

class Workflow(Base):
    __tablename__ = "workflows"
    
    id: Mapped[UUID]
    tenant_id: Mapped[UUID]
    slug: Mapped[str]           # URL-friendly identifier
    name: Mapped[str]
    config: Mapped[dict]        # JSONB workflow definition
    is_active: Mapped[bool]

Config JSONB Schema

{
  "nodes": [
    { "id": "node-greeter", "agent_name": "greeter-agent", "is_entry": true },
    { "id": "node-meal", "agent_name": "meal-agent", "skip_condition": "flags.skip_meal" },
    { "id": "node-feedback", "agent_name": "feedback-agent", "is_exit": true }
  ],
  "connections": [
    { "source_id": "node-greeter", "target_id": "node-meal", "context_passed": ["user_state"] },
    { "source_id": "node-meal", "target_id": "node-feedback" }
  ],
  "shared_context": ["user_id", "logged_meals", "captured_glucose"],
  "input_context": {
    "sources": {
      "user_profile": { "endpoint": "user.profile", "mappings": {...} }
    }
  },
  "output_context": {
    "endpoint": "voice.call.end",
    "fields": ["logged_meals"]
  }
}

Tool

Reusable declarative tool definitions.

class Tool(Base):
    __tablename__ = "tools"
    
    id: Mapped[UUID]
    tenant_id: Mapped[UUID]
    name: Mapped[str]           # Unique per tenant
    description: Mapped[str]
    config: Mapped[dict]        # JSONB tool definition
    is_active: Mapped[bool]

Config JSONB Schema

{
  "name": "save_meal",
  "parameters": [
    { "name": "meal_type", "type": "string", "enum": ["breakfast", "lunch", "dinner"] },
    { "name": "dishes", "type": "array", "required": true }
  ],
  "actions": [
    { "type": "validate", "rules": [...] },
    { "type": "context.set", "data": { "logged_meals[+]": "{{params}}" } },
    { "type": "webhook", "endpoint": "meal.log", "body": {...} }
  ],
  "on_success": [{ "type": "respond", "message": "Meal logged!" }],
  "on_failure": [{ "type": "respond", "message": "Failed to log meal." }]
}

Provider Key

BYOK (Bring Your Own Keys) storage with encryption.

class ProviderKey(Base):
    __tablename__ = "provider_keys"
    
    id: Mapped[UUID]
    tenant_id: Mapped[UUID]
    provider_type: Mapped[str]      # stt, tts, llm, vad
    provider_name: Mapped[str]      # deepgram, cartesia, gemini, etc.
    api_key_encrypted: Mapped[str]  # Fernet encrypted
    is_default: Mapped[bool]        # Default for this type

Provider Inheritance Chain

Platform Env → Tenant BYOK → Agent Override → Runtime

API Key

Server-to-server authentication.

class APIKey(Base):
    __tablename__ = "api_keys"
    
    id: Mapped[UUID]
    tenant_id: Mapped[UUID]
    name: Mapped[str]
    key_prefix: Mapped[str]     # as_live_, as_test_
    key_hash: Mapped[str]       # SHA-256 hash
    scopes: Mapped[list]        # ["*"] or ["agents:read", ...]
    rate_limit: Mapped[int]     # Requests per minute (nullable)
    is_active: Mapped[bool]

Call

Voice call history and transcripts.

class Call(Base):
    __tablename__ = "calls"
    
    id: Mapped[UUID]
    tenant_id: Mapped[UUID]
    workflow_id: Mapped[UUID]
    user_id: Mapped[str]        # External user identifier
    status: Mapped[CallStatus]  # pending, active, completed, failed
    started_at: Mapped[datetime]
    ended_at: Mapped[datetime]
    duration_seconds: Mapped[int]
    agent_history: Mapped[list] # ["greeter", "meal", "feedback"]
    transcript: Mapped[list]    # [{role, content, timestamp}, ...]
    context_snapshot: Mapped[dict]  # Final context state

User

Dashboard users for authentication.

class User(Base):
    __tablename__ = "users"
    
    id: Mapped[UUID]
    tenant_id: Mapped[UUID]     # FK to tenants
    email: Mapped[str]          # Unique globally
    name: Mapped[str]
    password_hash: Mapped[str]  # SHA-256 hashed password
    is_active: Mapped[bool]
    is_verified: Mapped[bool]
    last_login_at: Mapped[datetime]

Users authenticate via JWT tokens to access the dashboard and manage tenant resources.

Complete Hierarchy Example

TENANT (taphealth)
├── settings: { endpoints, languages, feature_flags, api_config }

├── PROVIDER_KEYS (BYOK)
│   ├── stt/deepgram (default), stt/sarvam
│   ├── tts/cartesia (default), tts/sarvam  
│   └── llm/gemini (default), llm/openai

├── TOOLS
│   ├── save_meal, log_glucose, log_steps
│   └── transfer_to_* (handoff tools)

├── AGENTS
│   ├── greeter-agent: { prompt, tools, handoffs → logging-agent }
│   ├── meal-agent: { prompt, tools: [save_meal], handoffs → glucose-agent }
│   ├── glucose-agent: { prompt, tools: [log_glucose], handoffs → feedback }
│   └── feedback-agent: { prompt, no tools, no handoffs (exit) }

├── WORKFLOWS
│   ├── daily-call: greeter → meal → glucose → exercise → feedback
│   └── welcome-call: welcome (single agent)

├── API_KEYS: { production, staging }

├── USERS: { admin@tenant.com, user@tenant.com }

└── CALLS: { history with transcripts and metrics }

On this page