Remember conversations in your app
Resume a conversation, render its history and extend the framework’s ActiveRecord models.
On this page
Source-backed MarkdownContinue a conversation
Pass the previous conversationId to the same agent with the same authenticated user and scope. The agent loads saved history before the next model turn and rejects a conversation belonging to a different user, scope or agent.
Treat conversation IDs as references, not permission. Apply the same ownership checks to your history and request-inspection routes. Direct text generation can append tracking rows to a conversation, but does not automatically replay its history; use an agent for that.
const result = await new HelpAgent({
user: userId, scope: teamId, conversationId, guidePath: 'help.md',
}).run('Which page should I open?');Work with the saved models
AiConversation groups a conversation. AiMessage stores text, tool lifecycle events and provider reasoning summaries when enabled. AiRequest stores inputs, responses, status, timestamps, provider metadata, token counts and costs. Root and child requests preserve the relationship between an agent run and its provider work.
The generated app registers these models, plus AiRateLimitBucket and AiRateLimitReservation, in its migrations. Use ordinary ActiveRecord queries from your app. Raw prompts and tool results can contain private data: authorize access and choose a retention policy for your application.
Render saved history
After authorizing the conversation, load its messages in sequence order and its requests. agentConversationTimeline() returns the message, tool and reasoning-summary items used by the streaming UI contract. Pass your agent’s tool definitions to attach app-specific tool labels.
The formatter can include prompt-context items for debugging. Filter those out of ordinary user views. It formats records; it does not authorize access or provide a UI component.
import { AiMessage, AiRequest, agentConversationTimeline } from '@db3.ai/app/ai';
const messages = await AiMessage.where({ conversation: conversationId }).orderBy('sequence').all();
const requests = await AiRequest.where({ conversation: conversationId }).all();
const timeline = agentConversationTimeline(messages, [], requests);Add your own fields
Create a model in your app that extends the framework model. This example adds a support topic while retaining the existing table and fields.
Replace AiConversation with your subclass in the app’s model registry, configure ai.models.conversation, generate a migration, review it and apply it. The service uses your configured constructor for its reads and writes. Register one class per table. If you change table names, also override the related link fields.
import { AiConversation } from '@db3.ai/app/ai';
import type { FieldBuilder } from '@db3.ai/app/db';
/** App-owned conversation fields appear in normal ActiveRecord queries and migrations. */
export class Conversation extends AiConversation {
/** Keeps the framework fields and adds an application label. */
static override fields(field: FieldBuilder) {
return { ...super.fields(field), topic: field.string({ default: 'support', index: true }) };
}
declare topic: string | null;
}
Use your model throughout AI
The same models option accepts request and message subclasses. The default user and scope fields store app identifiers; subclasses may replace them with typed links to your app’s models.
ai: {
...config.ai,
models: { conversation: Conversation },
},