Database schema
build_metadata(prefix="ffb_", metadata=None, embedding_dim=1024) defines four
tables. The DDL below is what it compiles to on PostgreSQL with the default
prefix; on SQLite the JSON columns are JSON and embedding is a JSON list of
floats.
| Table | Written by | One row per |
|---|---|---|
ffb_tool_calls | DatabaseSink | tool call |
ffb_events | DatabaseSink | event |
ffb_feedback_call_links | link_feedback | (feedback id, call) pair |
ffb_embeddings | EmbeddingSink, backfill | embedded text per source and model |
Keys and ordering
Section titled “Keys and ordering”idis the primary key offfb_tool_calls,ffb_eventsandffb_embeddings, a random UUID string.ffb_tool_callshas nocall_idcolumn;ffb_events.call_idandffb_feedback_call_links.call_idhold a call’sid.- Order by time, never by
id. Ids are random, so sort calls bystarted_at, events byoccurred_at, embeddings bycreated_at. - No foreign keys. An event or link can be written before its call leaves
the queue, and calls are pruned, so
call_idis a plain indexed column. - All timestamps are timezone-aware and written in UTC. SQLite drops the zone on read; the package’s own readers put it back.
ffb_tool_calls
Section titled “ffb_tool_calls”CREATE TABLE ffb_tool_calls ( id VARCHAR(36) NOT NULL, started_at TIMESTAMP WITH TIME ZONE NOT NULL, duration_ms FLOAT NOT NULL, tool VARCHAR(255) NOT NULL, mode VARCHAR(8) NOT NULL, ok BOOLEAN NOT NULL, outcome VARCHAR(16), -- ok, soft_error, error; NULL before 2026.09.27.4 error_type VARCHAR(255), error_message TEXT, session_id VARCHAR(255), request_id VARCHAR(255), client_id VARCHAR(255), user_sub VARCHAR(255), caller_kind VARCHAR(64), identity JSONB, server_version VARCHAR(64), args_size INTEGER, result_size INTEGER, args JSONB, -- SQL NULL in meta mode result JSONB, -- SQL NULL in meta mode extra JSONB, sample_rate FLOAT, -- set on sampled-in ok rows only PRIMARY KEY (id));CREATE INDEX ix_ffb_tool_calls_ok ON ffb_tool_calls (ok);CREATE INDEX ix_ffb_tool_calls_outcome ON ffb_tool_calls (outcome);CREATE INDEX ix_ffb_tool_calls_server_version ON ffb_tool_calls (server_version);CREATE INDEX ix_ffb_tool_calls_session_id ON ffb_tool_calls (session_id);CREATE INDEX ix_ffb_tool_calls_started_at ON ffb_tool_calls (started_at);CREATE INDEX ix_ffb_tool_calls_tool ON ffb_tool_calls (tool);CREATE INDEX ix_ffb_tool_calls_user_sub ON ffb_tool_calls (user_sub);Field meanings are in Records.
ffb_events
Section titled “ffb_events”CREATE TABLE ffb_events ( id VARCHAR(36) NOT NULL, occurred_at TIMESTAMP WITH TIME ZONE NOT NULL, kind VARCHAR(128) NOT NULL, key VARCHAR(255), call_id VARCHAR(36), -- ffb_tool_calls.id of the call it was recorded in session_id VARCHAR(255), user_sub VARCHAR(255), caller_kind VARCHAR(64), client_id VARCHAR(255), server_version VARCHAR(64), attrs JSONB, PRIMARY KEY (id));CREATE INDEX ix_ffb_events_call_id ON ffb_events (call_id);CREATE INDEX ix_ffb_events_key ON ffb_events (key);CREATE INDEX ix_ffb_events_kind ON ffb_events (kind);CREATE INDEX ix_ffb_events_occurred_at ON ffb_events (occurred_at);CREATE INDEX ix_ffb_events_user_sub ON ffb_events (user_sub);ffb_feedback_call_links
Section titled “ffb_feedback_call_links”CREATE TABLE ffb_feedback_call_links ( feedback_ref VARCHAR(255) NOT NULL, -- any feedback id: "42", "bug-7Q2X" call_id VARCHAR(36) NOT NULL, -- ffb_tool_calls.id position INTEGER NOT NULL, -- 0 = oldest linked call rule VARCHAR(32) NOT NULL, -- session or user_window linked_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (feedback_ref, call_id));CREATE INDEX ix_ffb_feedback_call_links_call_id ON ffb_feedback_call_links (call_id);Links are never pruned, and calls that have a link are never pruned either.
ffb_embeddings
Section titled “ffb_embeddings”CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE ffb_embeddings ( id VARCHAR(36) NOT NULL, source_type VARCHAR(16) NOT NULL, -- feedback, call_error, event, llm source_id VARCHAR(255) NOT NULL, -- feedback id, call id or event id model VARCHAR(128) NOT NULL, dim INTEGER NOT NULL, text_hash VARCHAR(64) NOT NULL, -- sha256 hex of text text TEXT NOT NULL, -- what was embedded, redacted and truncated created_at TIMESTAMP WITH TIME ZONE NOT NULL, embedding VECTOR(1024) NOT NULL, PRIMARY KEY (id), CONSTRAINT ffb_embeddings_source_uq UNIQUE (source_type, source_id, model));CREATE INDEX ffb_embeddings_hnsw ON ffb_embeddings USING hnsw (embedding vector_cosine_ops);CREATE INDEX ix_ffb_embeddings_created_at ON ffb_embeddings (created_at);CREATE INDEX ix_ffb_embeddings_source_id ON ffb_embeddings (source_id);CREATE INDEX ix_ffb_embeddings_source_type ON ffb_embeddings (source_type);VECTOR(1024) follows embedding_dim. The HNSW index exists on PostgreSQL
only. Embeddings with source_type = 'feedback' are never pruned.
Useful queries
Section titled “Useful queries”-- error rate per tool over the last day (PostgreSQL)SELECT tool, count(*) FILTER (WHERE NOT ok)::float / count(*) AS failure_rate, count(*) FILTER (WHERE outcome = 'soft_error') AS soft_errorsFROM ffb_tool_callsWHERE started_at > now() - interval '1 day'GROUP BY tool ORDER BY failure_rate DESC;
-- slowest callsSELECT tool, duration_ms, started_at FROM ffb_tool_callsORDER BY duration_ms DESC LIMIT 20;
-- the calls behind feedback 42, in orderSELECT l.position, l.rule, c.tool, c.outcome, c.error_messageFROM ffb_feedback_call_links lLEFT JOIN ffb_tool_calls c ON c.id = l.call_idWHERE l.feedback_ref = '42'ORDER BY l.position;The feedback table
Section titled “The feedback table”The feedback tools keep their items in a separate table named feedback
(no prefix), through synchronous SQLAlchemy, created on first use: id
(integer), type, title, description, submitter, contact_info,
status, created_at, updated_at. type and status are SQLAlchemy enums,
which store the enum member names (BUG, OPEN). list_feedback returns the
values (bug, open); get_feedback_statistics counts by the stored names.