AI conversations
What a chat turn writes to the database, the diagnostic trace kept alongside it, and how AI spend is recorded against your credit balance.
One chat turn writes to four tables. Your message and the reply go to
ai_messages under a thread in ai_conversations. A diagnostic record of how
the turn was answered goes to ai_turn_logs and ai_pipeline_events. The cost
goes to token_usage, which is also what moves your credit balance.
All five are yours: the policies check auth.uid() against the row's user_id,
or reach it through the conversation or turn log the row belongs to. The sixth
table on this page, ai_audit_findings, is a maintainer tool and is covered at
the end.
Threads and messages
ai_conversations is one row per thread.
| Column | What it holds |
|---|---|
deck_id | The deck the thread is attached to, when there is one. |
context_type | deck_sidebar, search, general or recommendations. |
parameters | Inputs for the run. A recommendation thread stores the intent and query here. |
is_favorite | Whether you starred the thread. |
created_at, updated_at | updated_at is touched on every turn, which is what orders the thread list. |
context_snapshot, ended_at, satisfaction and cards_added_count exist on
the table and nothing writes them today.
ai_messages gets two rows per turn, one for what you sent and one for the
reply.
| Column | What it holds |
|---|---|
role | user or assistant. |
content | The message text, stored verbatim. |
cards_mentioned | Card names pulled out of the reply's [[Card]] notation. |
tools_called | Which tools the model used on that turn. |
topics_detected | Topic labels from the classifier. |
input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens | Token counts for the reply. |
search_query and implicit_signal are unwritten columns.
A recommendation run stores itself the same way, as a thread with one user message holding your intent and one assistant message listing each suggested card with its reason.
The pipeline trace
Two tables record how a turn was answered rather than what it said. Both are diagnostic, both are readable only by you, and neither is read by the chat UI.
ai_turn_logs gets one row per turn: classifier_output and
domain_resolution for how the question was classified,
context_slots_loaded for which pieces of deck context were pulled in,
tool_calls_made, unresolved_themes, and token_usage and latency_ms as
per-stage JSON breakdowns.
ai_pipeline_events is the step-by-step trace under that. Events accumulate in
memory during the turn and are written in one insert after it finishes, ordered
by seq, so the trace costs the request nothing. kind is one of classifier,
context_load, domain_resolution, prompt_assembly, reasoning,
sonnet_call, tool_call, turn_complete and error, and data holds that
step's payload.
The payload is a record of the pipeline's own work, and it quotes what went
through it. A prompt_assembly or tool_call event can therefore contain parts
of your message and of your deck.
Spend
token_usage gets one row per billable AI action, whether or not it came from a
chat: deck analysis and recommendations write here too.
| Column | What it holds |
|---|---|
action_type | chat, recommendation, deck_analysis or semantic_search. |
model | The model that ran. |
tokens_input, tokens_output, tokens_cached | Token counts. A cached input token costs a tenth of a fresh one. |
cost_usd | What the call cost us at list rates. |
cost_with_markup | What was taken off your balance: cost_usd plus a 10% infrastructure markup. |
refunded_at | Set when a charge is refunded. |
deck_id, conversation_id, message_id | What the spend was attached to, where it applies. |
Every insert is followed by deduct_credits, which is the only path that lowers
credits_usd. Credit purchases land in the same table with an action_type of
credit_purchase, a negative cost_with_markup, and the Stripe payment intent
id in model, so a webhook delivered twice cannot credit you twice. Your
billing page reads the 50 most recent rows.
Retention
Nothing expires, and the app has no self-serve path for deleting a conversation today. Threads, messages, turn logs and pipeline events keep accumulating on the account. Ask us if you want a thread removed.
ai_audit_findings
This one holds no user content. A script run out of band samples recent pipeline
events, has a model critique each turn against a rubric, and writes what it
finds: a severity of low, medium or high, a category from a fixed list such
as classifier_misprediction or confident_wrong, a summary, and an optional
suggestion. Rows point at a turn log and carry no user_id.
Reads are restricted by policy to the maintainer list, and the script writes with the service role key. It exists so failures in the pipeline get found and fixed.