Skip to main content

Conversations and messages tables

Nine conversation-domain tables. The core is the messages message DAG (self-referential parent_id) — branches, regenerations, and text-selection sub-conversations all build on it (notation per the overview).

projects — projects

Containers for conversations and knowledge bases. 14 columns.

ColumnTypeNullableDefaultNotes
idTEXTno (PK)p_ + 12 hex chars
user_idTEXTnoFK→users(id), CASCADE
nameTEXTnoproject name; unique per (user_id, workspace_id) scope (see below)
descriptionTEXTno''description
instructionsTEXTno''project-level system instructions injected into its conversations' context
accentTEXTno'violet'theme color key (frontend palette)
emojiTEXTno''icon emoji
pinnedINTEGERno0pinned flag
kb_idTEXTyesNULLFK→knowledge_bases(id), SET NULL (project's default library; deleting the library keeps the project)
auto_add_uploadsINTEGERno0uploads inside the project automatically enter its knowledge base
created_atINTEGER → BIGINTnonow()creation time
updated_atINTEGER → BIGINTnonow()update time
is_publicINTEGERno1workspace sharing switch: 1 = shared with members, 0 = creator and workspace admins only. Ignored on personal rows
workspace_idTEXTno''※ ALTER-added: soft reference to workspaces.id; '' = personal
  • Indexes: idx_projects_user(user_id); the name-uniqueness index is replaced during migration with the three-column form idx_projects_user_name_unique ON (user_id, COALESCE(workspace_id,''), lower(trim(name))) (store.go:591-593) — the same user may reuse a name across personal space and workspaces.
  • Incoming: conversations.project_id (SET NULL).

conversations — conversations

22 columns.

ColumnTypeNullableDefaultNotes
idTEXTno (PK)c_ + 12 hex chars
user_idTEXTnoFK→users(id), CASCADE
project_idTEXTyesNULLFK→projects(id), SET NULL
titleTEXTno'新对话'title ("New conversation"); auto-rewritten by the task.title job
providerTEXTno''provider attribution snapshot (no FK)
model_idTEXTno''selected model (unconstrained snapshot — deleting the model leaves history intact)
fastINTEGERno0fast-mode conversation (real model resolved server-side, masked to users)
kb_idsTEXT (JSON)no'[]'attached knowledge base ids
rag_modeTEXTno'auto'retrieval policy: auto (server-routed) / inject (full injection). Legacy tool and unknown values normalize to auto (store/conversations.go:20-29)
summary_blocksTEXT (JSON)no'[]'summary blocks produced by context compaction
active_leaf_idTEXTyesNULLcurrent active leaf message id (soft reference, no FK — the DAG's "reading pointer")
provider_stateTEXT (JSON)no'{}'provider session state (e.g. OpenAI Responses replay state; the sandbox session id lives here too)
pinnedINTEGERno0pinned
archivedINTEGERno0archived. Workspace rows have no archive view — migration resets archived workspace rows to 0 (store.go:524-530)
starredINTEGERno0starred
inline_source_convTEXTno''text-selection sub-conversation: source conversation id; non-empty marks a hidden sub-conversation
inline_parent_idTEXTno''sub-conversation: anchored source message id
inline_quoteTEXTno''sub-conversation: quoted original text
is_publicINTEGERno0 (fresh installs)whether a workspace conversation is shared. Dialect nuance: CREATE defaults to 0 (fresh databases); upgraded databases receive it via ALTER with default 1 (preserving historical sharing), see store.go:226-229
created_atINTEGER → BIGINTnonow()creation time
updated_atINTEGER → BIGINTnonow()update time
workspace_idTEXTno''※ ALTER-added: soft reference to workspaces.id; '' = personal
  • Indexes: idx_conv_user(user_id), idx_conv_project(project_id), idx_conv_user_updated(user_id, archived, pinned DESC, updated_at DESC); migration also creates idx_conv_inline(inline_source_conv) and idx_conv_workspace_updated(workspace_id, archived, pinned DESC, updated_at DESC) (store.go:563,578).
  • Incoming: messages (CASCADE), message_feedback, user_feedback (SET NULL), conversation_shares (CASCADE), both lease tables (CASCADE), and documents/files/chunks conversation_id.

messages — messages (DAG)

30 columns. The self-referential parent_id forms a tree: branches and regenerations create sibling subtrees; conversations.active_leaf_id points at the active leaf.

ColumnTypeNullableDefaultNotes
idTEXTno (PK)msg_ + 12 hex chars; also the anchor for SSE reconnect (Last-Event-ID)
conversation_idTEXTnoFK→conversations(id), CASCADE
parent_idTEXTyesNULLFK→messages(id), CASCADE — the self-referential edge
roleTEXTnouser / assistant (the app only ever writes these two; no CHECK)
providerTEXTno''provider snapshot
model_idTEXTno''model that served the turn (unconstrained snapshot)
model_labelTEXTno''model display-name snapshot — stays readable after catalog renames/deletions
fastINTEGERno0this turn ran in fast mode; the user boundary masks model_id/model_label/provider
blocksTEXT (JSON)no'[]'content blocks with kinds text / thinking / tool_call / tool_output / citation / image / document / artifact / research / error (enum per llm/types.go:17)
rawTEXTyesNULLraw upstream response (kept when applicable)
stop_reasonTEXTyesNULLfree-text stop reason (no CHECK). Values emitted by the orchestrator/providers include: end_turn, stop, stopped (user stop), max_iterations, tool_budget_exceeded, content_moderation, quota_exceeded, insufficient_credits, error
attachmentsTEXT (JSON)no'[]'file references (pointing at files.id)
selected_user_skill_idsTEXT (JSON)no'[]'private skill ids applied to this user turn (branch/regeneration fidelity)
citationsTEXT (JSON)no'[]'RAG/search citations
input_tokensINTEGER → BIGINTno0input tokens
context_tokensINTEGER → BIGINTno0real prompt footprint of the final successful upstream request (0 = provider reported no usage)
output_tokensINTEGER → BIGINTno0output tokens
cache_read_tokensINTEGER → BIGINTno0cache read tokens
cache_write_tokensINTEGER → BIGINTno0cache write tokens
costREAL → DOUBLE PRECISIONno0turn cost (admin-visible)
currencyTEXTno'USD'cost currency
creditsREAL → DOUBLE PRECISIONno0credits charged to the user for this turn (user-facing, unlike admin-only cost)
statusTEXTno'complete'complete / streaming / error
errorTEXTno''failure detail when status='error'
gen_msINTEGER → BIGINTno0wall-clock generation time (ms)
search_textTEXTno''visible plain text projected at write time (text blocks only; excludes reasoning/tool/image data). Content search scans this column instead of LOWER()-ing the whole blocks JSON. Pre-existing rows were backfilled once (store.go:770-777)
verifyTEXT (JSON)no''Verify-mode auditor result {verdict, auditor_model_id?, auditor_label?, findings:[{severity,quote,issue}], at?} (llm/verify.go:39-46; ? keys omitted when empty); '' = never audited
created_atINTEGER → BIGINTnonow()creation time
feedbackTEXTno''※ ALTER-added (legacy compatibility mirror): old single-value rating '' / like / dislike (store/models.go:495). New writes go to message_feedback; legacy rows were migrated by BackfillLegacyMessageFeedback
author_idTEXTno''※ ALTER-added: speaker attribution inside shared workspace conversations (empty on personal rows)
  • Indexes: idx_messages_conv(conversation_id), idx_messages_parent(parent_id), idx_messages_conv_created(conversation_id, created_at), idx_messages_role_created(role, created_at), idx_messages_model_role_created(model_id, role, created_at).
  • Notes: a browser disconnect does not abort generation — the server keeps writing under a detached context (90-minute cap) and persists the answer; reconnects replay it from the event buffer (see Cloudflare proxy notes).

message_feedback — model quality feedback

One row per (assistant message, evaluating user). 12 columns.

ColumnTypeNullableDefaultNotes
idTEXTno (PK)mfb_ + 12 hex chars
message_idTEXTnoFK→messages(id), CASCADE
conversation_idTEXTnoFK→conversations(id), CASCADE
user_idTEXTnoFK→users(id), CASCADE
workspace_idTEXTno''snapshot (no FK)
model_idTEXTno''snapshot (no FK) — deleting a model never erases quality history
channel_idTEXTno''snapshot (no FK)
ratingTEXTnolike / dislike (CHECK)
reasonsTEXT (JSON) → JSONBno'[]' (PG '[]'::jsonb)dislike reason multi-select
commentTEXTno''optional note
created_atINTEGER → BIGINTnonow()creation time
updated_atINTEGER → BIGINTnonow()update time
  • Unique: UNIQUE(message_id, user_id). Indexes: five, covering updated_at, (model_id, updated_at), (rating, updated_at), conversation_id, (user_id, message_id).
  • Notes: admins read this from the user-feedback page; the legacy messages.feedback column is a compatibility mirror only and is no longer maintained by new write paths.

user_feedback — product issue reports

User-submitted product issues, unrelated to model quality. 15 columns.

ColumnTypeNullableDefaultNotes
idTEXTno (PK)uf_ + 12 hex chars
user_idTEXTnoFK→users(id), CASCADE
message_idTEXTyesNULLFK→messages(id), SET NULL (report survives message deletion)
conversation_idTEXTyesNULLFK→conversations(id), SET NULL
conversation_titleTEXTno''title snapshot — the report stays intelligible after thread deletion
descriptionTEXTnorequired problem description
page_pathTEXTno''frontend route where the issue happened
user_agentTEXTno''browser UA
viewport_widthINTEGERno0viewport width
viewport_heightINTEGERno0viewport height
screenshotBLOB → BYTEAyesNULLoptional page screenshot bytes in-database (not object storage — identical retention on local and object-storage deployments)
screenshot_mimeTEXTno''screenshot MIME
screenshot_widthINTEGERno0screenshot width
screenshot_heightINTEGERno0screenshot height
created_atINTEGER → BIGINTnonow()submission time
  • Indexes: (created_at DESC, id DESC), (user_id, created_at DESC).

conversation_shares — public shares

Host of read-only share links. 6 columns.

ColumnTypeNullableDefaultNotes
idTEXTno (PK)the public token itself: 192-bit hex from genToken(), used in /share/:id
conversation_idTEXTnoFK→conversations(id), CASCADE
user_idTEXTnoFK→users(id), CASCADE
titleTEXTno''title at share time
snapshotTEXT (JSON)no'[]'frozen JSON copy of the active message path at share time (cost/sensitive fields stripped) — later private messages never leak
created_atINTEGER → BIGINTnonow()creation time
  • Unique: idx_conv_shares_conv(conversation_id) — at most one live share per conversation; revoking = deleting the row, cutting access completely. Index idx_conv_shares_user(user_id).

memories — durable user facts

Facts extracted from conversations and maintained by the memory worker. 17 columns.

ColumnTypeNullableDefaultNotes
idTEXTno (PK)mem_ + 12 hex chars
user_idTEXTnoFK→users(id), CASCADE
memory_textTEXTnoone-sentence fact
memory_typeTEXTno''extractor-prompt taxonomy: location / preference / identity / schedule / habit / goal / constraint (llm/memory_worker.go:115; no CHECK)
slotTEXTno''noun key (e.g. favorite_language) — a new value in the same slot marks old rows STALE
valueTEXTno''concrete value of the slot
statusTEXTno'ACTIVE'ACTIVE (stable fact) / QUERY_DEPENDENT (currency depends on the question) / STALE (superseded) / UNKNOWN_CURRENT (conflict, currency unclear — llm/memory_worker.go:252-283). Only ACTIVE + QUERY_DEPENDENT are injected into system prompts
confidenceREAL → DOUBLE PRECISIONno0.80..1
source_message_idsTEXT (JSON)no'[]'provenance message ids
supersedesTEXT (JSON)no'[]'old memory ids this one replaces
superseded_byTEXT (JSON)no'[]'back-link (who replaced this)
affected_domainsTEXT (JSON)no'[]'affected slot domains
reasonTEXTno''status-change reason
valid_fromINTEGER → BIGINTyesNULLvalidity window start
valid_untilINTEGER → BIGINTyesNULLvalidity window end
created_atINTEGER → BIGINTnonow()creation time
updated_atINTEGER → BIGINTnonow()update time
  • Indexes: idx_memories_user_status(user_id, status), idx_memories_user_slot(user_id, slot).

conversation_compaction_leases — compaction lease

Cross-process mutex for context compaction (safe replicas even without Redis). 3 columns.

ColumnTypeNullableDefaultNotes
conversation_idTEXTno (PK)FK→conversations(id), CASCADE
owner_tokenTEXTnolease token (prevents releasing someone else's lease)
expires_atINTEGER → BIGINTnoexpiry in unix seconds
  • Index: idx_conversation_compaction_leases_expires(expires_at) (stale-lease sweep).

conversation_generation_leases — generation lease

Serializes one principal's appends on one branch. 5 columns.

ColumnTypeNullableDefaultNotes
conversation_idTEXTno (PK part)FK→conversations(id), CASCADE
branch_keyTEXTno (PK part)branch key
principal_idTEXTno (PK part)principal (user) id
owner_tokenTEXTnolease token
expires_atINTEGER → BIGINTnoexpiry
  • PK: composite (conversation_id, branch_key, principal_id). Index: expires_at.
  • Notes: other members, explicit branch edits, and regenerations are not blocked by this lock — they proceed independently.