Skip to main content
A ChatConversation record represents a single AI chat session within a workspace, persisting the full message history, model-side context snapshot, UI tab state, and runtime metadata needed to resume the conversation exactly where it left off. It is workspace-scoped (every row carries a mandatory workspace FK) and is used by both the web app and the browser extension, with the source column preventing each surface from polluting the other’s recency list. The entity belongs to the Activity category and is notable for being soft-delete-capable but not exposed through the standard data-views pipeline; it has no entry in overrides.yml or composites.yml.

API operations

Data model

Attributes

Relationships

System-computed

  • thread_id is the public resource identifier, generated via defaultRaw: gen_random_uuid() at INSERT time. The internal pk (auto-increment integer) is never exposed via the API.
  • created_at is set once by the MikroORM onCreate lifecycle hook (new Date()) and is never subsequently modified.
  • updated_at is set by both onCreate and onUpdate lifecycle hooks, reflecting the wall-clock time of every PATCH or write. It carries an explicit @Index decorator for ORDER BY performance.
  • deleted_at uses soft-delete semantics consistent with the rest of the platform, but ChatConversation is an explicitly listed exception in apps/api/CLAUDE.md to the standard soft-delete filter (‘append-only audit log’ category).
  • messages defaults to an empty array [] at entity construction. Parts within each message element use the PersistedPart discriminated union (type-tagged), making round-tripped history messages safely hydrable from JSONB.
  • tabs defaults to an empty array [] at entity construction. As of Migration20260522150000, tabs are keyed objects (PersistedTab[]) rather than the legacy string[]. The migration backfilled all existing rows: new_tab elements received gen_random_uuid() keys; all other destinations used their destinationId as the key.
  • active_tab_key replaced the legacy active_path varchar(512) column in Migration20260522150000. The migration extracted the active destination from active_path URLs via regex prefix matching against the known WORKSPACE_DESTINATIONS segment list, then dropped the active_path column.
  • total_tokens defaults to 0 at construction and is incremented by the AI chat service after each model call. It is never reset.
  • The compound index idx_chat_conversations_workspace_deleted (workspace_pk, deleted_at) was added in Migration20260416000000 specifically to accelerate the Hasura hot-path permission filter (workspace_pk = $1 AND deleted_at IS NULL).
  • The partial composite index idx_chat_conversations_workspace_source_updated (workspace_pk, source, updated_at DESC) WHERE deleted_at IS NULL was added in Migration20260427160000 to accelerate surface-filtered recency-sorted list queries (the web app and extension history panels each filter by their own source value).
  • source was backfilled by Migration20260427160000 for all pre-existing rows using a page_path heuristic: rows with a non-null page_path not starting with /workspaces/ were set to ‘extension’; all others were set to ‘web’. Only rows created via the API without a source value remain NULL.
  • There is no sourceWorkspaceConnector relation. ChatConversation rows are created exclusively by the chat service in response to user actions; they are not produced by connector syncs.
  • source is nullable (rows created via the API without a source field). The frontend’s surface-filtering logic treats NULL as ‘unknown’ and shows those conversations on both surfaces.

Example

Source: apps/api/src/database/entities/ChatConversation.ts · domain: workspace · tier: Activity