Everything below is the target schema for Model A. Status: in-build. Field-level RLS skeletons omitted for brevity — see §3.2.
sql
-- ───── Tier 0: tenants (verticals) ─────
create table tenants (
id uuid primary key default gen_random_uuid(),
slug text not null unique, -- sahayak, ranveer, kichcha, medicly, vakil, …
display_name text not null,
status text not null default 'active', -- active | paused | archived
created_at timestamptz not null default now()
);
-- ───── Tier 1: HUMAN (personas) ─────
create table humans (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null references tenants(id) on delete cascade,
slug text not null, -- sahayak, ranveer, kichcha, dr-vakil
display_name text not null,
voice jsonb not null default '{}', -- tone, register, signature phrasings
languages text[] not null default '{}', -- ISO codes; first is default
cultural_register text, -- e.g. 'hyderabadi-english', 'kannada-formal'
guardrails jsonb not null default '[]', -- persona-level rules
version int not null default 1,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (tenant_id, slug)
);
-- ───── Tier 2: COMPANY-TEAM (roles) ─────
create table teams (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null references tenants(id) on delete cascade,
slug text not null, -- pro-work, commerce-concierge, fertility-patient
display_name text not null,
department text, -- 'sales', 'support', 'commerce', 'clinical'
sops jsonb not null default '[]', -- ordered list of SOP blocks
escalation jsonb not null default '{}', -- when to handoff and to whom
allowed_handoffs uuid[] not null default '{}', -- team ids in same tenant
guardrails jsonb not null default '[]', -- team-level rules
version int not null default 1,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (tenant_id, slug)
);
-- join: which humans staff which teams
create table team_members (
team_id uuid not null references teams(id) on delete cascade,
human_id uuid not null references humans(id) on delete cascade,
primary key (team_id, human_id)
);
-- ───── Tier 3: AGENT (runtime configs) ─────
create table agents (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null references tenants(id) on delete cascade,
slug text not null, -- standard-productivity, commerce-payments, voice-fast
display_name text not null,
model_routing jsonb not null default '{}', -- {default: 'claude-sonnet-4', cheap: 'gemini-2.5-flash', …}
tools jsonb not null default '[]', -- [{name, schema, endpoint, auth}]
memory_policy jsonb not null default '{}', -- {episodic_topk: 5, facts: true, decay: 'recency'}
retrieval jsonb not null default '{}', -- {index: 'hnsw', metric: 'cosine', ef_search: 64}
guardrails jsonb not null default '[]', -- technical guardrails
max_tool_iterations int not null default 4,
version int not null default 1,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (tenant_id, slug)
);
-- ───── Composition: human + team + agent = deployable instance ─────
create table agent_bindings (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null references tenants(id) on delete cascade,
slug text not null, -- 'sahayak-pro-work', 'ranveer-commerce'
human_id uuid not null references humans(id),
team_id uuid not null references teams(id),
agent_id uuid not null references agents(id),
channels text[] not null default '{whatsapp,web}',
routing_key text, -- WA inbound 'to' number OR web domain
is_active boolean not null default true,
created_at timestamptz not null default now(),
unique (tenant_id, slug),
unique (routing_key)
);
-- ───── Versioning ─────
create table tier_config_versions (
id uuid primary key default gen_random_uuid(),
tier text not null check (tier in ('human','team','agent')),
tier_id uuid not null,
version int not null,
config jsonb not null,
created_by uuid,
created_at timestamptz not null default now(),
unique (tier, tier_id, version)
);
-- ───── End users & sessions ─────
create table users (
id uuid primary key default gen_random_uuid(),
phone text unique, -- identity key
display_name text,
locale text,
created_at timestamptz not null default now()
);
create table sessions (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references users(id) on delete cascade,
binding_id uuid not null references agent_bindings(id),
channel text not null,
started_at timestamptz not null default now(),
last_active_at timestamptz not null default now(),
state jsonb not null default '{}'
);
create index sessions_user_binding_active on sessions (user_id, binding_id, last_active_at desc);
-- ───── Turns ─────
create table messages (
id uuid primary key default gen_random_uuid(),
session_id uuid not null references sessions(id) on delete cascade,
direction text not null check (direction in ('inbound','outbound')),
channel text not null,
body text,
media jsonb,
metadata jsonb not null default '{}',
created_at timestamptz not null default now()
);
create index messages_session_created on messages (session_id, created_at desc);
create table tool_calls (
id uuid primary key default gen_random_uuid(),
session_id uuid not null references sessions(id) on delete cascade,
message_id uuid references messages(id),
tool_name text not null,
args jsonb not null,
result jsonb,
latency_ms int,
error text,
created_at timestamptz not null default now()
);
create table llm_calls (
id uuid primary key default gen_random_uuid(),
session_id uuid references sessions(id) on delete cascade,
binding_id uuid references agent_bindings(id),
provider text not null, -- 'anthropic', 'google', 'sarvam'
model text not null,
tokens_in int not null,
tokens_out int not null,
cache_hit_tokens int not null default 0,
cost_usd numeric(12,6),
latency_ms int not null,
ttft_ms int,
metadata jsonb not null default '{}', -- per-tier token breakdown
created_at timestamptz not null default now()
);
create index llm_calls_binding_day on llm_calls (binding_id, created_at desc);
-- ───── Memory ─────
create table memory_facts (
id uuid primary key default gen_random_uuid(),
scope text not null check (scope in ('user_binding','team','human')),
scope_id uuid not null, -- user×binding composite encoded, or team_id, or human_id
key text not null,
value jsonb not null,
source_message_id uuid references messages(id),
confidence numeric(3,2),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index memory_facts_scope on memory_facts (scope, scope_id, key);
create extension if not exists vector;
create table memory_embeddings (
id uuid primary key default gen_random_uuid(),
scope text not null, -- 'user_binding' | 'team' | 'human'
scope_id uuid not null,
kind text not null, -- 'episodic' | 'semantic' | 'document'
content text not null,
embedding vector(1536),
source_ref jsonb,
created_at timestamptz not null default now()
);
create index memory_embeddings_hnsw on memory_embeddings
using hnsw (embedding vector_cosine_ops);
create index memory_embeddings_scope on memory_embeddings (scope, scope_id);
-- ───── Guardrail violations ─────
create table guardrail_violations (
id uuid primary key default gen_random_uuid(),
session_id uuid references sessions(id),
binding_id uuid references agent_bindings(id),
tier text not null check (tier in ('human','team','agent')),
rule_id text not null,
severity text not null, -- 'block' | 'rewrite' | 'log'
outbound_was_blocked boolean not null default false,
context jsonb,
created_at timestamptz not null default now()
);