Is your feature request related to a problem? Please describe
ai_log is designed for auditing model calls, not retrieving visible chat messages. Building conversation history from it would require repeatedly parsing technical request and response payloads. It also does not reliably represent user messages, message states, or multiple conversations within one study session.
Describe the solution you'd like
Create ai_conversation and ai_message, then extend ai_log.
ai_conversation
| Column |
Sequelize type |
Nullable |
id |
INTEGER |
No |
userId |
INTEGER, FK user |
No |
studySessionId |
INTEGER, FK study_session |
No |
type |
INTEGER |
No |
deleted |
BOOLEAN |
No |
deletedAt |
DATE |
Yes |
createdAt |
DATE |
No |
updatedAt |
DATE |
No |
A conversation belongs to one study session but may span multiple study steps.
Conversation types:
const AI_CONVERSATION_TYPES = Object.freeze({
SIDEBAR: 0,
ADAPTIVE: 1,
});
Add:
ai_message
| Column |
Sequelize type |
Nullable |
id |
INTEGER |
No |
conversationId |
INTEGER, FK ai_conversation |
No |
studyStepId |
INTEGER, FK study_step |
Yes |
documentId |
INTEGER, FK document |
Yes |
aiModelId |
INTEGER, FK ai_model |
Yes |
role |
INTEGER |
No |
content |
TEXT |
No |
status |
INTEGER |
No |
metadata |
JSONB |
Yes |
deleted |
BOOLEAN |
No |
deletedAt |
DATE |
Yes |
createdAt |
DATE |
No |
updatedAt |
DATE |
No |
Message roles:
const AI_MESSAGE_ROLES = Object.freeze({
SYSTEM: 0,
USER: 1,
ASSISTANT: 2,
});
Message statuses:
const AI_MESSAGE_STATUSES = Object.freeze({
PENDING: 0,
COMPLETED: 1,
FAILED: 2,
ABORTED: 3,
});
Add:
CHECK ("role" IN (0, 1, 2))
CHECK ("status" IN (0, 1, 2, 3))
User and assistant messages store the model selected for their turn. System context messages use aiModelId = null because the same context may later be used by multiple models.
Messages are ordered by id ASC. Timestamps are used for display and analysis.
Deleting a conversation deletes its messages. Hard-deleting a referenced study step, document, or model sets the corresponding message reference to null so the transcript remains available.
Extend ai_log
| Column |
Sequelize type |
Nullable |
| aiMessageId | INTEGER, FK ai_message | Yes |
| totalLatencyMs | INTEGER | Yes |
| ttftMs | INTEGER | Yes |
ai_log.aiMessageId links a model call to its assistant message.
The new foreign keys use SET NULL: if a conversation or message is hard-deleted, its technical audit logs remain, but the deleted reference becomes null.
ttftMs remains null until streaming is implemented. The existing ai_log.status remains unchanged.
Indexes
Add:
ai_message(conversationId, id)
ai_log(conversationId)
ai_log(requestId)
These support conversation-history loading, request abortion, and the session-wide in-flight check.
Describe alternatives you've considered
Using only ai_log was rejected because it is an audit table containing technical request and response payloads. Retrieving visible conversations from it would require extensive parsing and would still not reliably represent user messages or message states.
Additional context
ai_message is the source of truth for visible conversation history and UI states.
ai_log is the source of truth for technical request state, auditing, in-flight checks, aborts, tokens, costs, and errors.
- Follow the existing
ai_budget and document constant patterns.
Acceptance criteria
- Up and down migrations complete successfully.
- Sequelize associations and constants work.
- Database checks reject unsupported conversation types, message roles, and message statuses.
- Messages load deterministically using
id ASC.
- Foreign-key deletion behavior works as described.
- Existing non-conversation
ai_log records remain valid.
- Relevant model and migration tests pass.
Is your feature request related to a problem? Please describe
ai_logis designed for auditing model calls, not retrieving visible chat messages. Building conversation history from it would require repeatedly parsing technical request and response payloads. It also does not reliably represent user messages, message states, or multiple conversations within one study session.Describe the solution you'd like
Create
ai_conversationandai_message, then extendai_log.ai_conversationidINTEGERuserIdINTEGER, FKuserstudySessionIdINTEGER, FKstudy_sessiontypeINTEGERdeletedBOOLEANdeletedAtDATEcreatedAtDATEupdatedAtDATEA conversation belongs to one study session but may span multiple study steps.
Conversation types:
Add:
ai_messageidINTEGERconversationIdINTEGER, FKai_conversationstudyStepIdINTEGER, FKstudy_stepdocumentIdINTEGER, FKdocumentaiModelIdINTEGER, FKai_modelroleINTEGERcontentTEXTstatusINTEGERmetadataJSONBdeletedBOOLEANdeletedAtDATEcreatedAtDATEupdatedAtDATEMessage roles:
Message statuses:
Add:
User and assistant messages store the model selected for their turn. System context messages use
aiModelId = nullbecause the same context may later be used by multiple models.Messages are ordered by
id ASC. Timestamps are used for display and analysis.Deleting a conversation deletes its messages. Hard-deleting a referenced study step, document, or model sets the corresponding message reference to null so the transcript remains available.
Extend
ai_log|
aiMessageId|INTEGER, FKai_message| Yes ||
totalLatencyMs|INTEGER| Yes ||
ttftMs|INTEGER| Yes |ai_log.aiMessageIdlinks a model call to its assistant message.The new foreign keys use
SET NULL: if a conversation or message is hard-deleted, its technical audit logs remain, but the deleted reference becomes null.ttftMsremains null until streaming is implemented. The existingai_log.statusremains unchanged.Indexes
Add:
These support conversation-history loading, request abortion, and the session-wide in-flight check.
Describe alternatives you've considered
Using only
ai_logwas rejected because it is an audit table containing technical request and response payloads. Retrieving visible conversations from it would require extensive parsing and would still not reliably represent user messages or message states.Additional context
ai_messageis the source of truth for visible conversation history and UI states.ai_logis the source of truth for technical request state, auditing, in-flight checks, aborts, tokens, costs, and errors.ai_budgetanddocumentconstant patterns.Acceptance criteria
id ASC.ai_logrecords remain valid.