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 typeProvider Inheritance Chain
Platform Env → Tenant BYOK → Agent Override → RuntimeAPI 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 stateUser
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 }