System and audit tables
Two system-domain tables. Aivory has no schema_migrations-style bookkeeping table — migrations run as embedded schema + idempotent ALTERs + backfill marker rows inside settings (see the overview's migration section).
settings — global key/value configuration
Most runtime configuration lives here, not in environment variables — saved from the admin UI it takes effect on the next request without restart; across replicas it invalidates via the Redis cfg:invalidate channel (cmd/api/main.go:82-90). 3 columns.
| Column | Type | Nullable | Default | Notes |
|---|---|---|---|---|
key | TEXT | no (PK) | — | configuration key |
value | TEXT | no | — | JSON-encoded: "true" carries quotes; arrays/objects likewise. Bare text and JSON values are not interchangeable |
updated_at | INTEGER → BIGINT | no | now() | last write time |
Boot-seeded keys (store.Seed, store.go:1118-1232)
All seeded via INSERT INTO settings(key, value) VALUES(?, ?) ON CONFLICT(key) DO NOTHING (store.go:1220) — admin edits are never overwritten. Values shown as JSON literals:
| Key | Seed value | Meaning |
|---|---|---|
default_model_id, task_model_id, title_model_id, file_route_model_id, context_compaction_model_id, tool_route_model_id, image_prompt_model_id, verify_model_id | "" | Model-policy slots (Model policy admin page); all empty = chat is non-functional |
rag_full_text_threshold | 8000 | Documents at/below this estimated token count are injected in full; above it they are vectorized and chunk-retrieved |
rag_top_k | 8 | retrieved chunk count |
rag_dynamic_topk | false | take every chunk above the similarity threshold instead |
rag_similarity_threshold | 0.5 | cosine cutoff for dynamic mode |
rag_rerank_enabled / rag_rerank_api_url / rag_rerank_api_key / rag_rerank_model | false / "" / "" / "" | optional OpenAI-format reranking (independent of chat channels, knowledge-base retrieval only) |
credit_preflight_enabled | true | estimate prompt tokens before generation and refuse if the user cannot afford the turn |
keep_recent_rounds | 6 | rounds kept verbatim by compaction |
summary_max_tokens | 8192 | summary block token cap |
compaction_request_max_tokens | 32768 | compaction request cap |
context_compaction_prompt | "" | custom compaction prompt |
compaction_token_trigger | 32000 | automatic compaction trigger |
compaction_token_cap | 80000 | compaction ceiling |
compaction_token_target_percentage | 60 | post-compaction target share |
compaction_retention_percentage | 40 | retention share |
compaction_enabled | true | automatic compaction switch |
memory_enabled | true | long-term memory switch |
daily_message_limit / daily_image_limit | from DAILY_MESSAGE_LIMIT(200) / IMAGE_DAILY_LIMIT(30) | daily quota seeds |
daily_token_limit | 0 | daily token cap (0 = unlimited; backs the daily_token windows in quota_ledger) |
max_concurrent_generations | 3 | per-user concurrent generations |
password_login_enabled | true | allow password logins |
auth_entry_mode | "login_page" | login entry form factor |
auth_default_provider_id | "" | default social login provider |
oauth_initial_password_policy | "required" | whether OAuth-created accounts must set a password |
oauth_auto_provision_enabled | true | auto-create accounts on first OAuth login |
signup_open | true | registration open |
email_verification_required | false | new accounts start as users.status='pending' when on |
email_domain_whitelist | "" | registration domain allowlist |
register_ip_daily_limit | 0 | accounts per client IP per day (0 = unlimited) |
register_captcha_required / login_captcha_required | false | slider-puzzle captcha gates |
credits_per_usd | 0 | 1 USD of model cost = N credits; 0 disables credits platform-wide |
settlement_currency | "USD" | single administrator-chosen currency for user-facing prices |
card_purchase_url | "" | external card-purchase link |
disabled_tools | [] | platform-disabled built-in tool ids |
sandbox_base_url / sandbox_api_key | "" / "" | sandbox sidecar connection (admin page can override the env values) |
storage_provider | "local" | object storage backend: local / s3 / aliyun_oss ("" = off) |
storage_archive_ttl_days | 30 | sandbox workspace archive retention (0 = keep forever) |
max_image_upload_mb | 5 | image upload soft cap (MAX_UPLOAD_BYTES stays the absolute ceiling) |
moderation_keywords / moderation_model_id / moderation_categories / moderation_message | [] / "" / 7 default categories / default copy | content moderation word list, model, and blocked-message text |
log_full_requests / log_errors_only / log_request_bodies | false / true / true | logging privacy switches (govern usage_logs.request_* retention) |
announcement | {"enabled":false,"title":"","body":"","image_url":"","remember_dismiss":true,"require_read":false,"updated_at":0} (seed default, store.go:1218) | the single global notice (image_url non-empty = image notice; updated_at doubles as the dismiss version stamp) |
In addition, the one-time backfill/cleanup idempotency markers are rows of this very table (the complete set: oauth_pwset_backfill_v1, msg_search_text_backfill_v1, user_sort_order_backfill_v1, user_onboarded_backfill_v1, user_group_billing_prices_backfill_v2, message_feedback_backfill_v1, daily_token_quota_ledger_backfill_v1, user_tool_mode_settings_cleanup_v1) — they are Aivory's "migration bookkeeping". Retired keys are deleted by migration (summary_target_percent, summary_merge_max_tokens, store.go:32-35,469).
store.go:1176-1177 describes register_captcha_required as an "arithmetic captcha (text math question)"; the actual implementation is a slider-puzzle captcha (/api/public/captcha, internal/api/captcha.go). Behavior wins over the comment.
pending_storage_cleanup — delete-purge queue
The pre-deletion manifest of the async account purge (§8.1-A). 3 columns.
| Column | Type | Nullable | Default | Notes |
|---|---|---|---|---|
path | TEXT | no (PK) | — | object-storage key / local path awaiting physical unlink (the PK doubles as the dedupe) |
user_id | TEXT | no | — | owning user. Deliberately no FK — the user row may already be gone while the manifest must survive |
created_at | INTEGER → BIGINT | no | now() | registration time |
- Protocol: register paths first → run destructive SQL → unlink bytes and delete rows one by one; whatever remains is swept at startup. The table being non-empty at any moment only means a previous physical purge was interrupted; an app restart converges it (operational SQL example 8 in the overview).
Related pages
- Platform operations — announcement, logging, legal and backup admin UIs
- Database overview and operations — migration mechanism and operational SQL