Sadie is Postgres-only. The full, authoritative schema is packages/db/src/schema.ts: roughly 1.6k lines of Drizzle table definitions with typed JSONB columns via .$type<T>(). This page groups the tables by domain and calls out notable shapes; for column-level detail, read the file.
| Table | Notes |
|---|
users | Better-Auth-compatible (id, email, emailVerified, name, image) plus Sadie fields (displayName, avatarColor, role). One row per deployment. This is a single-user product. |
sessions | Cookie sessions. Indexed on token. |
accounts | Better-Auth account rows; reserved for future SSO. |
verifications | Better-Auth verification tokens. Unused today. |
| Table | Notes |
|---|
conversations | sadieEmotion column carries one of seven emotions; set per-message and persisted on the conversation. |
messages | status is `streaming |
chat_context_attachments | Removable context chips: kind is `discourse |
| Table | Notes |
|---|
studio_workspaces | A user can have many workspaces. colorToken picks the Clay accent. |
documents | Tiptap + Yjs. ydocState is base64-encoded binary Yjs update. wikiEntryId set when the draft is filed into Memory. |
comments | Text-anchored via anchorFrom/anchorTo integer offsets. |
studio_collaborator_grants | Local-only collaborator grants; no email send yet. |
| Table | Notes |
|---|
sources | Library of everything Sadie reads. kind covers notes, docs, past posts, articles, RSS, newsletters, Notion, Obsidian, Readwise, uploads. weighting is optional: `personal_library |
source_weightings | Audit trail when weighting changes. |
| Table | Notes |
|---|
notetaker_connections | Per-user provider connection with encrypted API/webhook secrets, tag scopes, status, and provider metadata. |
notetaker_processed_calls | Dedup marker keyed by user, provider, and external call id. Final markers carry either signalCount or skippedReason; transient fetch/extraction failures leave no final marker so retry/reconcile can try again. |
Provider auth failures can move a notetaker connection to error, which pauses future ingestion until the user reconnects. Retryable provider failures such as rate limits, network errors, and 5xx responses do not finalize a processed-call marker and keep the connection active. The reconcile cursor advances after successful provider listing, but retryable unfinalized calls keep the cursor before the failed call so later reconciles can retry it.
Notion sync stores provider-level issue stats on notion_connections and advances compile_cursors.last_notion_sync_at after completed pull-sync passes, using the same timestamp as last_sync_at.
| Table | Notes |
|---|
feeds | Per-user tracked handle (x_account, rss, newsletter) with durable health fields for last success, last failure, consecutive failures, and retry-after timing. |
feed_items | Per-user items. Retained for backward compat with code that queries by feed id. |
global_feed_items | Deduplicated across users, keyed by (source, externalId). One X API call per handle, fanned out to all subscribers. |
user_feed_item_links | Join table from user feeds to global_feed_items. |
feed_theme_counts | Rolling theme aggregates for feed-to-wiki promotion. |
| Table | Notes |
|---|
wiki_entries | kind is `person |
wiki_patch_events | Provenance trail. Before/after hashes, trigger kind, source ID. Idempotent by (wikiEntryId, afterHash). |
wiki_link_candidates | Candidate cross-links awaiting review before becoming durable graph edges. |
knowledge_nodes | Graph nodes. kind is `wiki |
knowledge_edges | relation is `mentions |
| Table | Notes |
|---|
soul_state | Typed items: `audience |
voice_portrait | 8-dimension voice model with exemplars, systemPrompt, rewriteAdjustments per dimension. |
soul_narrative | Six prose sections: work_context, personal_context, top_of_mind, three tiers of brief_history_*. |
preference_candidates | Observed preferences before promotion to Soul. Three kind-families: legacy generic, substance, voice. |
rewrite_events | Captured when the user edits Sadie’s output. dimensionDeltas array feeds voice adjustments. |
| Table | Notes |
|---|
briefs | Editorial syntheses. anchorWikiIds and sourceFeedItemIds track provenance. status is `draft |
| Table | Notes |
|---|
discourse_opportunities | Today cards. Carry whyNow, whyYou, whoTalking (DiscourseVoice[]), angles (DiscourseAngle[] with confidence, engagementScore, topCritique), rank, accent. status is `active |
| Table | Notes |
|---|
policies | Deterministic guardrails. kind is `regex |
policy_violations | Audit trail per match. |
| Table | Notes |
|---|
agent_versions | Immutable config snapshots. config JSONB holds prompts, weights, policies, catalog. parentVersionId forms the lineage. status is `committed |
compile_runs | Audit log for every background job. kind covers onboarding, incremental, lint, discourse_refresh, soul_promotion, soul_decay, agent_evolution. |
compile_cursors | Per-user watermarks for incremental processing. |
lint_findings | Wiki health-check surface. kind enumerates orphan, stale, weak provenance, contradiction, missing page, low linkage, soul contradiction, soul decayed. |
| Table | Notes |
|---|
sadie_settings | JSONB payload with persona defaults, cadences, AI provider preference, encrypted userApiKeys, engagement weights, XP weights, currentAgentVersionId. |
onboarding_state | step tracks progress through the 8-step flow. data JSONB accumulates calibration, rewrite drills, scenario checks, and learned writing weights. |
| Table | Notes |
|---|
activity | Everything the user does in the app, keyed by (kind, subjectKind, subjectId). Feeds the agent evolution XP calculation and the Memory activity timeline. |
Many columns use .$type<T>() to brand their JSONB with a TypeScript type. Compile-time only; Postgres still stores real JSONB and can index into it. Notable types:
SadieSettingsPayload. Everything in sadie_settings.payload.
AgentConfig. Prompts + weights + policies + catalog per version.
VoiceDimension[] / VoiceExemplar[]. Voice portrait contents.
NarrativeSection[]. Soul narrative.
OnboardingData. Calibration + distillation state.
DiscourseAngle[] / DiscourseVoice[]. Today card shapes.
See packages/db/src/schema.ts for every branded type. Drizzle returns these as typed values; the database stores them as plain JSONB.