AgentifUI Database Design
Scope
This document summarizes the current PostgreSQL schema used by AgentifUI as of November 15, 2026, including multi-backend conversation runs, generation governance and native human input.
The source of truth is the SQL migration set under database/migrations/. This document intentionally describes the current schema shape and operating model rather than copying every column from every table.
Current Migration Set
The repository currently bootstraps and evolves the schema with these public SQL migrations:
20260214010100_add_missing_rpc_functions.sql20260214020100_create_local_pg_baseline_schema.sql20260214061000_add_external_identity_profile_tables.sql20260214133000_enforce_single_idp_binding.sql20260214153000_create_better_auth_tables.sql20260214161000_add_local_login_policy_controls.sql20260214192000_add_better_auth_phone_fields.sql20260214201000_add_fallback_password_profile_metadata.sql20260215030000_m4_rpc_rls_guc_hardening.sql20260215050000_m4_table_rls_phase2.sql20260215070000_m4_table_rls_phase3.sql20260215080000_m4_rls_strict_mode_switch.sql20260215170000_m6_realtime_outbox_cdc.sql20260216012000_create_migration_sync_checkpoints.sql20260216023000_preserve_explicit_updated_at_in_trigger.sql20260217153000_add_error_events_table.sql20261110113000_create_better_auth_sso_providers.sql20261113150000_add_backend_conversation_runs.sql20261114160000_add_agent_submission_governance.sql20261114170000_protect_pending_agent_credentials_and_quota.sql20261114180000_allow_trusted_dify_conversations.sql20261115090000_add_agent_human_input.sql20261115100000_add_agent_resume_dispatch_fence.sql
This is a schema inventory, not an instruction to replay initialization SQL on an existing database. Apply only reviewed pending migrations during upgrades; see deployment or Docker upgrades.
Key Design Facts
- The project no longer relies on Supabase-managed auth tables such as
auth.users. - Auth persistence lives in local PostgreSQL tables created for better-auth.
- Business data, auth data, external identity data, and operational telemetry are stored in separate table groups.
- RLS is part of the current design, not a future plan.
- Redis and MinIO are important runtime dependencies, but they are not part of the PostgreSQL schema itself.
Table Groups
1. Authentication and Identity
better-auth persistence tables
auth_usersauth_sessionsauth_accountsauth_verificationsauth_sso_providers
Purpose:
- store authenticated users, sessions, provider accounts, verification tokens, and optional phone-number fields
- support email, SSO, and phone-oriented auth flows in application code
Notes:
auth_users.emailis uniqueauth_sessions.tokenis uniqueauth_accounts(provider_id, account_id)is unique- phone-number support is added by
20260214192000_add_better_auth_phone_fields.sql auth_sso_providersis the Better Auth SSO plugin’s persistence table, added by20261110113000_create_better_auth_sso_providers.sql; it is distinct from the application’ssso_providerstable and does not replace it
Business-facing identity tables
profilesuser_identitiesprofile_external_attributessso_providersdomain_sso_mappingsauth_settingsauth_local_login_audit_logs
Purpose:
profilesstores business-facing user metadata, role, status, and login stateuser_identitiesbinds external issuer/subject identities to internal usersprofile_external_attributesstores IdP/HR-style immutable attributes under a guarded update modelsso_providersanddomain_sso_mappingsdefine SSO configurationauth_settingsholds login-policy data, including the active local-login mode; legacy fields are not proof that every flag is enforced by the current authentication runtimeauth_local_login_audit_logsrecords local-password fallback decisions and outcomes
Important details:
profiles.idis the application user key used across business tablesuser_identitiesis constrained to at most one identity row per user after20260214133000_enforce_single_idp_binding.sqlprofile_external_attributesis protected by a trigger so business-side updates cannot casually overwrite identity-sync fieldsprofilesalso stores fallback password metadata added in20260214201000_add_fallback_password_profile_metadata.sql
2. App Catalog and Access Control
providersservice_instancesapi_keysgroupsgroup_membersgroup_app_permissionsai_configs
Purpose:
providersandservice_instancesdefine upstream AI/provider targets and app entriesapi_keysstores encrypted credentials and usage metadatagroups,group_members, andgroup_app_permissionsmodel group-based access controlai_configsremains available for app/provider configuration state
Important relationships:
service_instances.provider_id -> providers.idapi_keys.provider_id -> providers.idapi_keys.service_instance_id -> service_instances.idgroup_members.group_id -> groups.idgroup_members.user_id -> profiles.idgroup_app_permissions.group_id -> groups.idgroup_app_permissions.service_instance_id -> service_instances.id
3. User Content and Execution Data
conversationsmessagesapp_executionsuser_preferencesapi_logs
Purpose:
conversationsstores user conversation sessionsmessagesstores ordered conversation messagesapp_executionstracks workflow/text-generation execution state and outputsuser_preferencesstores per-user UI preferencesapi_logsstores provider call metadata
Important details:
conversations.user_id -> profiles.idmessages.conversation_id -> conversations.idmessages.user_id -> profiles.idapp_executions.user_id -> profiles.idapp_executions.service_instance_id -> service_instances.id- message ordering uses
created_at,sequence_index, andidfor stable sorting - indexes exist for conversation timelines, app execution lookup, and provider usage tracing
4. Trusted Backend Runs And Generation Governance
backend_conversationsbackend_runsagent_submissionsbackend_run_interactions
Purpose and relationships:
backend_conversations.conversation_id -> conversations.idbinds a local conversation to a service instance, backend type, configuration fingerprint and server-observed native thread. It supports LangChain and explicitly negotiated Dify conversations; it is not a client-writable metadata field.backend_runsbelongs to that conversation/instance binding and records the website user, submission identity, native run mapping and bounded result. Execution and persistence statuses are separate, so upstream success is not automatically a saved result. Ordinary turn runs and human-input resume runs share a root run; resume segments do not create fake user questions.agent_submissionsis the cross-backend generation admission/identity ledger, with request/target fingerprints, credential versions, task state and quota reservation/counting state. It links the effective group permission when one supplies the allowance. It is not an exactly-once execution guarantee or a general billing/audit system.backend_run_interactions.origin_run_id -> backend_runs.idstores one controlled native interruption per origin run, including its prompt, expiration, decision and associated resume run. Native checkpoint and interrupt identifiers are server-owned; browser-visible IDs do not grant access.
All four tables enable and force RLS with explicit system-actor policies. Public product routes must authorize the website user and application before performing the required server-context operations. Generic data APIs must not become an alternative writer for trusted bindings, runs or messages.
Database constraints/triggers protect conversation identity, one active run per
conversation, root/resume relationships, pending credentials and quota records.
The human-input migrations add waiting to execution status and a guarded
pre-dispatch claim-release path. A waiting task, a closed event-stream segment
and a completed task are not interchangeable states.
Legacy Dify conversations retain their compatible browser-managed persistence; the negotiated Dify and LangChain paths use the server writer. Read/refresh/history does not dispatch a generation or a human decision. See generation governance, AG-UI integration and LangChain integration for exact supported states and reconciliation boundaries. Native thread/run data and bounded result/checkpoint payloads can contain sensitive information; include them in restricted database backups, not public diagnostic exports.
5. Operational Tables
realtime_outbox_eventsmigration_sync_checkpointserror_events
Purpose:
realtime_outbox_eventscaptures DB changes for the realtime pipelinemigration_sync_checkpointssupports migration/incremental-sync workflowserror_eventspersists normalized error envelopes and aggregation metadata
Important details:
realtime_outbox_eventsis filled by triggers on key business tableserror_eventsstores fingerprint, code, source, severity, request metadata, and occurrence counters
Enum Types
The baseline schema defines these enums:
user_roleaccount_statusmessage_rolemessage_statusexecution_typeexecution_statussso_protocol
These are part of the durable contract and are referenced by business tables.
The newer backend-run, submission and interaction states use their own checked
text columns; they are not all values of the baseline execution_status enum.
Inspect the later migrations before changing lifecycle queries or constraints.
Security Model
RLS
RLS is enabled and forced on the main business tables through the M4 migrations.
The application sets request-scoped PostgreSQL settings such as:
app.current_user_idapp.current_user_roleapp.rls_system_actorapp.rls_strict_mode
Relevant runtime code:
lib/server/pg/session-options.tslib/server/pg/user-context.tslib/server/pg/pool.ts
Strict Mode
APP_RLS_STRICT_MODE=1 changes legacy behavior so an implicit no-actor context no longer bypasses RLS checks. Only an explicit system actor can do that.
Encrypted Secrets
Provider/API keys are not stored in plaintext by design. The application encrypts them before writing to api_keys, using API_ENCRYPTION_KEY.
Operational Patterns
Auth/Profile synchronization
The auth system and business profile system are related but intentionally separate.
In practice:
- authenticated users persist in
auth_users - session resolution ensures a corresponding
profilesrow exists for business logic - external identity and immutable HR-style metadata are stored in dedicated side tables
This avoids overloading the auth tables with business-specific profile concerns.
Realtime pipeline
realtime_outbox_events is the supported foundation for reliable realtime delivery. It decouples DB commits from downstream broker publication and is the mode expected by current Fastify realtime routes.
Error telemetry
error_events is the durable store for normalized application errors, including the frontend client error-event path that now terminates in Fastify.
Bootstrap and Migration Guidance
For a fresh environment, apply the SQL files in lexical order:
set -euo pipefail
shopt -s nullglob
for migration in database/migrations/*.sql; do
psql "$MIGRATOR_DATABASE_URL" -v ON_ERROR_STOP=1 -f "$migration"
done
Use isolated databases for dev, test, and prod. Do not point those profiles at the same PostgreSQL database.
Use the migration-owner connection for schema changes, then confirm the non-superuser runtime role’s grants/default privileges and strict-RLS behavior. The Docker initializer is the supported one-shot path for a fresh Docker database; the loop above is a host-side fresh schema example, not a replacement for role/storage/content bootstrap. Application startup does not run migrations. For an existing installation, use only the pending ordered files and preserve additive tables on compatible code rollback; check unresolved work before changing the deployed interaction implementation.
What This Document Does Not Claim
This document does not attempt to replace the SQL files.
If a code review, migration, or production issue depends on exact column definitions or trigger bodies, inspect the corresponding file under database/migrations/ directly.