Skip to content

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.

TableWritten byOne row per
ffb_tool_callsDatabaseSinktool call
ffb_eventsDatabaseSinkevent
ffb_feedback_call_linkslink_feedback(feedback id, call) pair
ffb_embeddingsEmbeddingSink, backfillembedded text per source and model
  • id is the primary key of ffb_tool_calls, ffb_events and ffb_embeddings, a random UUID string. ffb_tool_calls has no call_id column; ffb_events.call_id and ffb_feedback_call_links.call_id hold a call’s id.
  • Order by time, never by id. Ids are random, so sort calls by started_at, events by occurred_at, embeddings by created_at.
  • No foreign keys. An event or link can be written before its call leaves the queue, and calls are pruned, so call_id is 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.
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.

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);
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.

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.

-- 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_errors
FROM ffb_tool_calls
WHERE started_at > now() - interval '1 day'
GROUP BY tool ORDER BY failure_rate DESC;
-- slowest calls
SELECT tool, duration_ms, started_at FROM ffb_tool_calls
ORDER BY duration_ms DESC LIMIT 20;
-- the calls behind feedback 42, in order
SELECT l.position, l.rule, c.tool, c.outcome, c.error_message
FROM ffb_feedback_call_links l
LEFT JOIN ffb_tool_calls c ON c.id = l.call_id
WHERE l.feedback_ref = '42'
ORDER BY l.position;

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.