The tables
firstflow_llm_calls
The core observability row. Key columns:
user_id,provider(openai/anthropic),model,operation(chat/embedding)input_tokens,output_tokens,total_tokens,cache_read_tokens,cache_write_tokensinput_cost_usd,output_cost_usd,total_cost_usd(computed from model pricing)latency_ms,status(success/error),error_message,http_statustrace_id,span_id,metadata(jsonb)prompt_messages,completion_textonly whencaptureContent: true
firstflow_conversation_messages
Recorded when a call carries a conversationId/sessionId and a userId: the prompting user turn and the assistant reply, sharing the same trace_id/span_id.
conversation_id,user_id,role,contentmodel,provider, token and cost columns,latency_ms,finish_reason,http_statusis_error,error,properties(jsonb)
firstflow_conversations
One row per conversation, upserted with running aggregates and (optional) analysis:
id(the conversation id),user_id,message_counttotal_input_tokens,total_output_tokens,total_cost_usd,error_count,last_message_atstate(jsonb) holds the active intent;intent,outcome,tags,notessentiment,sentiment_score,topics, andeval_*columns
The
intent, sentiment, and related columns require migration
0007_conversation_signals.sql. If a classifier runs but these never update,
apply that migration. See Set up Supabase.firstflow_traces
The parent row for a withTrace operation: id, user_id, name, input_state, output_state, latency_ms, is_error, error, metadata, started_at, ended_at.
Custom persistence
The tables above are what the shipped Supabase adapter writes. The SDK records through aFirstflowLLMPersistence interface, so you can target another store by implementing it (recordCall, optional recordTrace/recordMessage/tagConversation/recordSentiment/recordIntent, …). See Configuration.