Schema guide — what the columns mean and who uses them¶
Status: Current | Last reviewed: 2026-09-01
A developer's data dictionary for the TrueServe Postgres database (trusrv_agents).
For each core table it explains what the columns are for and which services
read or write them, so you can reason about the blast radius of a change without
spelunking the SQL. This is the narrative companion to the terse
tables.md catalog, the rename-map.md, and the
SQL functions reference.
Conventions (enforced by review + migration-conventions.md): snake_case columns;
*_idforeign keys (agent_id,tenant_id);*_attimestamps;is_*/has_*booleans;config/metadatafor JSONB blobs; tenant-scoped tables carrytenant_id+ RLS. Nolegacy_*/old_*columns — if you see one, it's a cleanup target (the 2026-06-20 squash removed the last ones).
The services that touch the DB¶
| Service | Lang | Access | Role |
|---|---|---|---|
| ai-manager | TS (Next.js) | pg, centralized in lib/db*.ts + lib/schema-*.ts |
The authoring/admin plane — writes agents, workflows, tools, integrations, catalogs, tenants, branding. |
| ai-orchestrator | Python | psycopg2 + asyncpg, scattered | Real-time agent runtime. Reads agent config via resolve_agent_version(); writes conversation transcripts. |
| conversation-api | Python | asyncpg, centralized in db_queries.py |
Conversation/transcript read + search API; serves surfaces; A2A card host. |
| async-agent | Python | asyncpg | Workflow runtime. Reads workflow config via resolve_workflow_version(); writes execution state. |
| agent-host | Python | asyncpg | Tool/MCP execution host; reads tool + workflow config. |
| ari-controller-node | TS | pg, centralized in database.ts |
Telephony bridge. Reads agent_versions, ccaas_pipeline_integrations, integrations. |
| media-proxy / genesys/webex/five9 connectors (Go) | Go | database/sql |
Read agent config / integration config for their channel. |
Asterisk (func_odbc.conf) |
— | ODBC, live | Reads agent_versions (extension → agent) on every inbound call. |
Kamailio (kamailio.cfg) |
— | sqlops, live | Reads sip_provider_endpoints + sip_digest_auth only. |
The two live, non-app readers (Asterisk, Kamailio) are why a handful of columns/tables must never be renamed without a lockstep config change — see rename-map.md §5.
Agents: identity vs versioned config¶
The single most important split in the schema. agents = identity; all runtime
config lives on agent_versions.config JSONB. Nothing reads agent config off
agents (the legacy agents.config column and the agent_configs view were
dropped in migration 104).
agents — one row per agent (identity + team-wide flags)¶
| Column | Meaning | Used by |
|---|---|---|
id (text PK) |
The agent id. Text, because it appears in URLs, SIP extensions, and embed stamps (e.g. 3023). New ids are short numeric strings allocated from public.agent_id_seq; they are globally unique across tenants, not tenant-local counters. |
everyone |
name, description |
Display metadata. | ai-manager (write), surfaces (read) |
pipeline_type, subtype |
Classification: pipeline_type ∈ speech_to_speech/text_to_text/speech_to_text; subtype is one of the four supported archetypes — voice_agents, chat_agents, copilots, translation (the orphaned voice_changing/transcription/stt_translation/transcription_enhancement/custom subtypes were removed 2026-06-20; see apps/ai-manager/lib/pipeline-types.ts). Denormalized here for fast list-filtering; also on agent_versions and inside config. Indexed. |
ai-manager agent list (filter), Asterisk chart configmap (SELECT pipeline_type FROM agents) |
surface_config_id (uuid) |
FK → surface_configs; the embed/branding surface this agent renders in. |
conversation-api, surface-app |
tenant_id (uuid) |
Multi-tenancy owner; RLS key. | everyone |
created_by (uuid) |
FK → users. |
ai-manager |
is_template, template_category, template_metadata |
If is_template, this row is an agent blueprint the New Agent wizard clones (not a live agent). |
ai-manager |
tags (text[]) |
Free-form labels for the agent list. | ai-manager |
a2a_enabled (bool), a2a_metadata (jsonb) |
Agent2Agent expose state. Deliberately columns, not config — the collab Yjs allow-list would silently drop a config key. |
conversation-api (serves the A2A card), ai-manager |
debug_enabled (bool) |
Per-agent debug capture toggle. | ai-orchestrator, ai-manager |
generation_job_id (uuid) |
If set, a "Generate with AI" run owns this placeholder; the editor shows the generation panel instead of the canvas. | ai-manager |
agent_versions — versioned runtime config¶
PK (agent_id, version_number). agent_id is the bare FK to agents.id
(renamed from base_agent_id in the 2026-06-20 squash).
| Column | Meaning | Used by |
|---|---|---|
agent_id (text) |
FK → agents.id. |
everyone |
version_number (int) |
Draft is version 0; published versions are 1+. |
resolve/publish functions |
status |
'draft' or 'published'. |
resolve functions, ai-manager |
is_current_published (bool) |
Exactly one published version per agent is the live one. Unique-indexed. | resolve_agent_version, traffic split |
config (jsonb) |
The entire pipeline: stages, settings, output lanes, multimodality, tools refs. TS shape in pipeline-types.ts; field-by-field in config-jsonb.md. | ai-manager (write via collab), ai-orchestrator (read) |
pipeline_type, subtype |
Denormalized copy (see agents); returned by the resolve functions. |
ai-orchestrator, ari-controller |
traffic_weight (int) |
A/B split weight across published versions. | resolve_agent_version_weighted |
catalogs (jsonb) |
Pinned A2UI catalog URIs, set at publish time independently of config. |
conversation-api catalog_manifest, ai-manager |
created_from_version, published_at, published_by, publish_notes |
Publish audit trail. | ai-manager, publish function |
tenant_id |
RLS key. | everyone |
How it's read: never SELECT … FROM agent_versions ad-hoc — go through
resolve_agent_version(versioned_id, allow_draft). It parses 3023, 3023:v2,
3023:draft and returns the right (agent_id, version_number, config, …) tuple.
See sql-functions.md.
Workflows: same identity/version split, numeric ids¶
workflow_configs (identity) + workflow_versions (config). The cutover from
text slugs to numeric ids happened in migration 130 — workflows are addressed
by the numeric workflow_configs.id; the old legacy_slug was dropped in the
2026-06-20 squash.
| Table.column | Meaning | Used by |
|---|---|---|
workflow_configs.id (integer PK) |
The workflow id. Integer, internal-only. | async-agent, agent-host, ai-manager |
workflow_configs.{name,description,is_active,is_template,tags} |
Identity + classification. | ai-manager |
workflow_configs.config (jsonb) |
Phases/nodes/edges (the workflow graph). TS shape in workflow-types.ts; field-by-field in config-jsonb.md. | ai-manager (write), async-agent (read) |
workflow_configs.{expose_mcp,mcp_tool_name} |
If exposed, the workflow is callable as an MCP tool. | async-agent, agent-host |
workflow_versions.workflow_id (integer) |
FK → workflow_configs.id (renamed from base_workflow_id; it is integer, matching workflow_configs.id). |
async-agent, ai-manager |
workflow_versions.{version_number,status,is_current_published,config,traffic_weight} |
Same draft/published model as agents. | resolve_workflow_version |
Read via resolve_workflow_version() / _weighted, mirroring the agent functions.
Conversations & transcripts¶
| Table | What | Written by | Read by |
|---|---|---|---|
conversations |
One row per session: id, session_id, agent_id (may carry a :version suffix), caller_id, call_source, status, started_at/ended_at/duration_ms, config_version, is_test, tenant_id, multimodal_config_id, archive columns. |
ai-orchestrator (conversation_store) |
conversation-api, ai-manager history |
conversation_messages |
Per-message transcript detail (31 cols) — role, content, turn_id, plus the normalization lifecycle columns (variant, revision, message_key, audible_start_ms, provider, …). |
ai-orchestrator | conversation-api (transcript + SSE) |
conversation_events |
Pipeline events (tool calls, A2UI renders, lifecycle). | ai-orchestrator | conversation-api, surfaces |
conversation_search_documents |
One denormalized, tenant-scoped row per conversation for full-text/faceted search. Note: carries BOTH agent_id (the versioned id used) and base_agent_id (the bare id, for grouping) — the only place base_agent_id legitimately survives. |
trigger-maintained | conversation-api search |
conversation_recordings / _attachments / _archive_* |
Media + the disabled-by-default archive-to-object-storage subsystem. | ai-orchestrator, archive worker | conversation-api |
agent_id on conversations is the bare-or-versioned id the session ran; auth
checks must split(":",1)[0] before comparing to agents.id.
Tools & integrations (the encryption boundary)¶
| Table | What | Notes |
|---|---|---|
tool_types / integration_types |
Reference catalogs of available tool/integration kinds (mcp, openapi, python_sandboxed, a2a, knowledge, …; openai, genesys, salesforce, …). integration_types is compatibility metadata for legacy type_id rows; new add-integration UI is driven by the source/capability catalog in ai-manager. Seeded by 001_reference_data.sql. |
Adding a provider/source edits the source catalog, runtime resolver helpers, and any capability-specific routes. |
tool_configs |
A configured tool instance: type_id, name, config (jsonb), integration_id (FK), input_schema/parameters, is_active, tenant_id. |
ai-manager (write); ai-orchestrator/async-agent/agent-host (resolve + execute) |
integrations |
A configured provider profile: source_id (Amazon, Google, Salesforce, BeyondPresence, …), enabled_capabilities (LLM, Telephony, OCR, Events, PII Redaction, Avatar, …), legacy type_id, config (jsonb, non-secret settings like region/org id), scope (platform/tenant), is_active, is_default. Query new use cases by source/capability and keep type_id only for saved-reference compatibility. |
ai-manager (write); all runtimes (read via helper resolution + config->>'key') |
integration_secrets / tool_secrets |
Encrypted credentials (encrypted_value/iv/auth_tag, AES-GCM under MASTER_ENCRYPTION_KEY). Never put secrets in *.config. |
written by ai-manager on save; decrypted by runtimes |
tool_health |
Periodic MCP/tool reachability probe results; drives the Tools page "Unreachable"/"Drift" badge. | ai-manager tool-health worker |
Source fields can be marked discoverable in the ai-manager source catalog. The /api/integrations/discover/* routes merge unsaved draft config/secrets with saved integration secrets when editing, then return dropdown options for capability-specific fields without storing anything. Amazon discovery covers Connect instance/contact-flow options for telephony and SQS queue ARN/URL pairs for events. Google discovery covers Pub/Sub subscriptions/topics for events, BigQuery/GCS resources for storage, and Document AI processors for ocr. Azure discovery covers OpenAI deployments for llm, Document Intelligence models for ocr, and Speech voices for text_to_speech / avatar. Unsupported capabilities still save manual config values.
See secret-storage.md.
Telephony & CCaaS routing¶
| Table | What | Live reader |
|---|---|---|
ccaas_pipeline_integrations |
Default-deny allow-list: (pipeline_id, integration_id, enabled) — which CCaaS integrations may route inbound to which agent. Keyed on the bare agent id; controlled only from the publish dialog. (Renamed from genesys_pipeline_integrations in mig 194; the compat view was dropped in the squash.) |
ari-controller-node validates every inbound CCaaS call against it |
sip_provider_endpoints |
Per-provider SIP/media settings (host, transport, media_profile, CIDRs). |
Kamailio reads live — do not rename |
sip_digest_auth, sip_credentials |
SIP auth. | Kamailio / Asterisk |
ps_endpoints / ps_auths / ps_aors |
Asterisk PJSIP realtime config. Upstream Asterisk ARA column names — do not rename. | Asterisk |
sip_logs |
Kamailio writes SIP message logs here (tenant-scoped). | Kamailio (write) |
Tenancy, auth & RLS¶
tenants(UUID id; the TrueServe platform tenant is00000000-…-001, and seed tenants use vanity ids like carrier0000c477-…/fedexf0000000-…-fede).tenants_id_uuid_formatis a shape-only CHECK (8-4-4-4-12hex), NOT the RFC-4122 v4/v7 version/variant nibbles — it'sNOT VALIDand re-fires on every UPDATE, so a stricter regex made vanity-id rows un-updatable (edit/deactivate 500s).tenant_memberships,groups/group_members,users(is_platform_adminbypasses RLS on reads).api_tokens(tsf_bearer tokens, scoped) +api_token_audit_log.management_oauth_*(hashed, tenant/resource-bound management OAuth grants with rotating refresh-token families). The olderoauth_clients/oauth_*tables are a separate plaintext legacy surface and must not back management MCP/API tokens.embed_*holds single-use embed JWTs + signing keys,allowed_origins(CORS /frame-ancestors).- RLS: tenant-scoped tables enable row-level security keyed on
current_setting('app.current_tenant_id'). ai-manager wraps every query inwithTenantContext(); backend services connect astsf_appwhich hasBYPASSRLS(granted by002_tsf_app_bypassrls.sql) and set tenant context explicitly where needed. In prod,099_normalize_user.sqlstrips superuser so RLS is enforced.
Catalogs, generation, and templates (admin-plane)¶
a2ui_catalogs/_catalog_versions/_components/_tools/_tool_plugins— the A2UI component catalog system (tenant-scoped; built-ins live on the TrueServe tenant).generation_jobs— "Generate with AI" runs;agents.generation_job_id/workflow_configs.generation_job_idpoint back at the owning run.assistant_sessions— per-target CLI sessions.access_mode='edit'backs the editor Assistant;access_mode='read'isolates advisory Management API questions and cannot expose draft-save tools.pipeline_templates(stage/subagent),prompt_templates, agent-template rows (agents.is_template) — the four template stores.surface_configs/tenant_themes/sidebar_custom_tabs— branding + UI surfaces.trigger_rules— event→workflow bindings (workflow_config_idinteger FK,event_type,source_filter,criteria, lifecycletrigger_type).
Migrations & how this stays true¶
The schema is one squashed baseline (000_full_schema.sql) + 001/002/003/004/099,
forward-only (overview.md). A fresh boot must re-pg_dump to the
committed baseline byte-for-byte — schema-drift.yml
enforces it, plus a renamed-column grep guard.
Populated environments migrate in place with
migrate-city-db.sh
(runbook).
The JSONB inside config is its own contract: generated into JSON Schema by
generate-config-schemas.ts
and drift-guarded by config-schema-drift.yml.
005_normalize_config_shapes.sql normalised the config JSONB to one canonical
shape per node/stage (router outputs, ocr_extract fields, translation stage-level
language/voice, dead-key cleanup) — see config-jsonb.md and the
realign backlog.