AIDB emits OpenTelemetry (OTel) traces, logs, and metrics for its own function calls, background workers, and SQL query execution. For the GUCs that turn this feature on and configure it, see Configuring AIDB parameters — OpenTelemetry tracing.
Schemas
When aidb.otel_client = 'database', AIDB writes traces, logs, and metrics into three tables in the aidb_otel schema:
aidb_otel.traces— one row per span.aidb_otel.logs— one row per log record.aidb_otel.metrics— one row per batch of metric readings, such as a periodic collection of instrument values.
A span describes an operation and its timing. AIDB stores log messages as separate OTel log records, not as events embedded inside spans. A log record can link to its span through the trace_id and span_id columns. Governance decisions are spans named purpose_decision in aidb_otel.traces.
This follows OpenTelemetry's recommendation to record new events as logs correlated with spans. The Span Events API is being deprecated; this doesn't remove span events from the OTel data model or invalidate existing data.
EDB Agent Governance reads spans and log records directly from the database. It doesn't require an OTLP collector and doesn't display all AIDB telemetry. Export to an external monitoring system is configured separately through the exporter worker.
By default, AIDB grants read access to these tables to aidb_governance, not to aidb_users. Administrators can grant a separate login role read access to the tables it needs. For the dashboard, use the dedicated viewer role: membership in aidb_governance also permits retention cleanup and purpose management.
All three tables share a common set of columns, plus a few columns specific to each.
Common columns
Every row in all three tables carries these columns:
| Column | Type | Description |
|---|---|---|
id | bigint | Row identity. |
recorded_at | timestamptz | When the row was written. |
payload_json | jsonb | The item as an OTLP protojson Export{Metrics,Logs,Trace}ServiceRequest. Populated when aidb.otel_client_storage_format is json or both. |
payload_binary | bytea | The same item as raw OTLP protobuf wire bytes. Populated when aidb.otel_client_storage_format is binary or both. |
export_status | text | pending, exported, or failed. Only meaningful when the exporter worker is enabled — stays pending forever otherwise. |
export_attempts | integer | Number of export attempts made so far. |
next_export_attempt_at | timestamptz | Earliest time the exporter will retry this row. NULL once exported or permanently failed. |
last_export_error | text | Error message from the most recent failed export attempt. |
source | text | internal for telemetry emitted by AIDB itself; external for telemetry submitted by other callers. Added in AIDB 7.9.0. |
principal | text | The database login (session_user) that wrote the row, for both internal and external telemetry. This is separate from the calling user recorded in a governance decision. Added in AIDB 7.9.0. |
At least one of payload_json/payload_binary is always populated — see Storage format.
Starting in 7.9.0, use source to distinguish AIDB's own decisions from decisions reported by external senders. Rows retained from an earlier version are labeled internal during upgrade and have no principal; their original source can't be verified from those columns.
aidb_otel.traces — additional columns
Each row is exactly one span. The identifier and attribute columns are stored separately from the payload, so you can query them without parsing JSON or protobuf.
| Column | Type | Description |
|---|---|---|
trace_id | text | 32-character lowercase hex trace ID. Indexed (traces_trace_id_idx). |
span_id | text | 16-character lowercase hex span ID. |
parent_span_id | text | 16-character lowercase hex parent span ID. NULL for a root span. |
pg_session_id | text | The db.postgresql.session_id attribute, when present. Partial index excluding NULL (traces_pg_session_id_idx). |
gen_ai_conversation_id | text | The gen_ai.conversation.id attribute, when present. Partial index excluding NULL (traces_gen_ai_conversation_id_idx). |
mcp_session_id | text | The mcp.session.id attribute, when present. Partial index excluding NULL (traces_mcp_session_id_idx). |
decision_status | text | The aidb.agent.decision.action_status attribute (allowed or denied), when present — only the one purpose_decision span per enforcement decision carries it. Partial index excluding NULL (traces_decision_status_idx). Lets a per-agent allowed/denied rollup over a time window use an index range scan instead of scanning payload_json. See Purpose-enforcement decisions. |
decision_id | text | The aidb.agent.decision.decision_id attribute, when present. Links a denied call to its decision record. |
agent_name | text | The gen_ai.agent.name attribute, when present. |
span_status_code | integer | OTel span status: 0 (unset), 1 (OK), or 2 (error). This is separate from the governance decision's allowed/denied status. |
span_start_time | timestamptz | The start time supplied by an external telemetry sender. NULL for AIDB's own stored spans; their timing remains in the payload. Added in AIDB 7.9.0. |
span_end_time | timestamptz | The end time supplied by an external telemetry sender. NULL for AIDB's own stored spans. Added in AIDB 7.9.0. |
recorded_at is the time the row was stored, which can be later than the operation itself. Use the span's start and end times to measure its duration.
trace_id/span_id/parent_span_id are lowercase hex, the same representation payload_json's own traceId/spanId/parentSpanId fields use — the two match as strings, so you can look up a row by either. (This isn't the same as payload_binary's raw OTLP protobuf bytes fields, which aren't hex or text at all.)
aidb_otel.logs — additional columns
Each row is one log record, with two columns linking it back to the span it was emitted under:
| Column | Type | Description |
|---|---|---|
trace_id | text | Trace ID of the span this log record was emitted under. NULL if emitted outside any active span. Indexed (logs_trace_id_idx). |
span_id | text | Span ID of the span this log record was emitted under. NULL under the same condition as trace_id. |
Configuring storage format
The aidb.otel_client_storage_format GUC controls which payload column the database client (one of the aidb.otel_client destinations — see Configuring AIDB parameters) writes to: payload_json, payload_binary, or both — the two columns described under Common columns. Values:
| Value | payload_json | payload_binary | Notes |
|---|---|---|---|
json | ✅ | — | Default. Human-readable, directly queryable with standard jsonb operators. |
binary | — | ✅ | Raw OTLP protobuf. Cheaper to produce (no intermediate JSON tree), but not directly queryable. |
both | ✅ | ✅ | Both populated from the same underlying data. Roughly doubles storage and write cost. |
aidb.otel_client_storage_format only affects the database client (aidb.otel_client = 'database'). It has no effect on noop (nothing is written), stdout or grpc (both always emit the full OTLP payload directly, not through these columns), or log, which always writes payload_json regardless of this setting, since a server log line is a human-readable debugging aid, not storage a collector later forwards.
When exporting a row that has both payloads populated (storage_format = both), the exporter worker sends only payload_binary, since it's cheaper to have produced.
Starting in 7.9.0, telemetry ingested from external senders retains the format received: JSON or protobuf. aidb.otel_client_storage_format controls AIDB's own telemetry, not these incoming records.
Triggering retention cleanup
The cleaner background worker deletes old rows automatically (see Running background workers), but this function lets you trigger a cleanup pass immediately instead of waiting for its next tick.
aidb.run_otel_retention_cleanup()
Manually triggers a full retention cleanup pass, deleting rows older than aidb.otel_cleaner_retention_days from all three aidb_otel tables, in the same bounded batches the cleaner background worker uses. Runs immediately, regardless of aidb.otel_cleaner_enabled — useful for an ops task or a test that shouldn't wait for the worker's own wake-up interval.
Requires the aidb_governance role (see Managing audit access) — aidb_users has no EXECUTE on this function.
Takes no arguments and returns void. Safe to call while the cleaner background worker is running — the two don't compete over the same rows.
SELECT aidb.run_otel_retention_cleanup();
Note
There's no equivalent manual trigger for the exporter. Forwarding to an external collector only ever runs from the exporter worker's own tick, so that a delivery in progress never shares a transaction with anything else that could roll it back and risk sending the same row twice.
Internally, the database client writes through aidb_otel.store_metrics(), aidb_otel.store_logs(), and aidb_otel.store_traces(). These aren't meant to be called directly — aidb_users has EXECUTE on them (needed for any traced call to work) but no SELECT on the tables they write to.
Running background workers
Two background workers run per database, each spawned only when enabled for that database (see Configuring AIDB parameters for the governing GUCs).
Exporting traces, logs, and metrics
Export is disabled by default. Enable aidb.otel_exporter_enabled and configure aidb.otel_exporter_endpoint to forward stored telemetry. EDB Agent Governance reads the database directly and doesn't need this worker.
Forwards pending, due rows from aidb_otel.{traces,logs,metrics} to the OTLP/HTTP collector at aidb.otel_exporter_endpoint, on a 15-second tick. payload_json rows are sent as OTLP/HTTP+JSON. payload_binary rows are sent as OTLP/HTTP+protobuf (Content-Type: application/x-protobuf) — a real OTel Collector's HTTP receiver dispatches on that header, so both formats share the same endpoint. Every outbound request goes through AIDB's egress allowlist.
Failed deliveries back off per row (not per tick), so one row's retry schedule never blocks the rest of the batch. After aidb.otel_exporter_max_retries attempts, a row is marked export_status = 'failed' and stops being retried automatically — find these with:
SELECT * FROM aidb_otel.traces WHERE export_status = 'failed';
Cleaning up expired rows
Deletes rows older than aidb.otel_cleaner_retention_days from all three tables, in batches of aidb.otel_cleaner_batch_size, on an hourly tick. Set aidb.otel_cleaner_retention_days = 0 to disable cleanup entirely without stopping the worker itself.
Traced attributes
AIDB records attributes automatically when telemetry is enabled. It follows OpenTelemetry semantic conventions where applicable (gen_ai.*, db.*, error.type), with custom aidb.* attributes for other AIDB concepts. The reference below covers the attributes emitted by AIDB on spans, logs, and metrics. Each section identifies where they appear; an attribute can be absent when it doesn't apply to the operation.
Database telemetry from AIDB 7.8.0 uses schema version 1; AIDB 7.9.0 introduces schema version 2. The schema version identifies the telemetry structure and changes independently of the AIDB release number. Upgrading doesn't add new attributes to older records. These fields are available in the stored telemetry; the dashboard displays a subset.
Setting resource attributes
Set once per process, shared by every signal (traces, logs, and metrics):
| Attribute | Description |
|---|---|
service.version | The AIDB extension version that produced the data. |
service.name | Always aidb. Identifies AIDB when a monitoring system receives data from multiple services. |
aidb.otel_schema_version | The telemetry schema version: 1 in AIDB 7.8.0 and 2 starting in 7.9.0. |
aidb.database.name | Name of the database the backend is connected to. |
aidb.database.oid | OID of that database. |
Setting root-span attribute
Set once per trace, only on its root span:
| Attribute | Description |
|---|---|
db.postgresql.session_id | Correlates a trace with the Postgres server log lines from the same backend. See below for the format and where it's set. |
db.postgresql.session_id uses the format <start_time_hex>.<pid_hex>, matching Postgres's own %c log_line_prefix escape. It's per-connection, so it's set only on each trace's root span — not on every span, and not as a resource attribute — since sharing it with the metrics pipeline would inflate metric cardinality. It's promoted to the indexed pg_session_id column on aidb_otel.traces.
Recording Agent Hub attributes
Set on spans for agent conversations, tasks, and messages:
| Attribute | Description |
|---|---|
gen_ai.operation.name | Static operation label on agent conversation spans. |
gen_ai.agent.name | Agent name. |
gen_ai.agent.id | Agent ID. |
gen_ai.conversation.id | Conversation ID. Promoted to the indexed gen_ai_conversation_id column on aidb_otel.traces. |
aidb.agent.model | Model used by the agent. |
aidb.agent.purpose | The agent's declared purpose (see Permissions). |
aidb.agent.status | Outcome of the agent call: COMPLETE, TIMEOUT, or ERROR. |
aidb.agent.task.id | ID of one agent invocation, distinct from the conversation ID. Absent for read-only calls. |
aidb.agent.message.id | ID of the message requested by a get_message lookup. |
aidb.agent.system_prompt.chars | Character count of the system prompt, including agent instructions and attached plugins. Added in AIDB 7.9.0. |
aidb.agent.plugin.count | Number of plugins attached to the agent. Added in AIDB 7.9.0. |
aidb.read_only | Whether the call was read-only. Shared with Tools Hub — not agent-exclusive. |
error.type | Error classification on a failed call: timeout or _OTHER. The error message is recorded separately in logs. |
Recording Tools Hub attributes
Set on spans for tool calls, including those imported from an MCP server:
| Attribute | Description |
|---|---|
gen_ai.tool.name | Tool name. |
aidb.mcp.server_name | Name of the MCP server a tool was imported from. |
aidb.mcp.url | MCP server URL. Recorded as-is, without redaction; restrict read access to telemetry accordingly. |
aidb.mcp.transport | MCP transport: streamable_http or sse. |
aidb.tool.call_count | Number of tool calls in a batch invocation. |
aidb.sleep.seconds | Requested duration, in seconds, of a native pause/sleep tool call. |
mcp.session.id | MCP session ID, when a tool call happens within one. Promoted to the indexed mcp_session_id column on aidb_otel.traces. |
Recording purpose-enforcement decisions
With telemetry enabled, purpose-scoped tool SQL produces purpose_decision spans with these attributes. Agents without a purpose don't produce these decision records.
| Attribute | Description |
|---|---|
aidb.agent.decision.decision_id | Unique ID for this decision. Also embedded in the error message text when the action is denied, so an error can be linked back to its decision record. |
aidb.agent.decision.action_status | allowed or denied — the only two values. Promoted to the indexed decision_status column on aidb_otel.traces. |
aidb.agent.decision.interaction_point | The kind of interaction being decided. Currently always sql_exec (SQL execution by a tool) — the only interaction point implemented so far. |
aidb.agent.decision.principal | The calling user's Postgres identity, captured before switching to the purpose's role. |
aidb.agent.decision.agent_role | The purpose-resolved Postgres role the action ran (or would have run) as. |
aidb.agent.decision.denial_reason | Present only when denied. One of role_not_found (the purpose's role doesn't exist), not_role_member (the caller isn't a member of the purpose's role), escalation_blocked (the SQL tried to change the running role with SET ROLE, RESET ROLE, or SET SESSION AUTHORIZATION, or to change a privilege-restricted setting), engine_privilege_denied (Postgres rejected the SQL with a 42501 permission error), or pre_execution_rejected (the tool SQL failed validation before execution). Not promoted to its own column. |
aidb.agent.decision.effective_purpose | The purpose actually enforced for the decision. It can differ from the agent's configured purpose when a delegated agent runs under its caller's purpose. Added in AIDB 7.8.0. |
aidb.agent.decision.referenced_relations | A JSON array stored as a string, listing distinct schema-qualified relations named by the SQL statement after parsing and analysis. The list is limited to 5 KiB. Added in AIDB 7.9.0. |
aidb.agent.decision.referenced_relations_total | The number of distinct relations before the list is shortened to fit the limit. Added in AIDB 7.9.0. |
aidb.agent.decision.denial_class | A category for a PostgreSQL privilege error, such as object_privilege, sequence_privilege, or rls_policy. Added in AIDB 7.9.0. |
aidb.agent.decision.denial_funcname | The PostgreSQL function reported as the source of the error, when available. Useful when investigating a denial. Added in AIDB 7.9.0. |
aidb.agent.decision.denial_message | The database's error message, limited to 5 KiB. It can contain sensitive data. Added in AIDB 7.9.0. |
db.response.status_code | The PostgreSQL SQLSTATE. On a decision span, this is 42501 for a PostgreSQL privilege error. Added in AIDB 7.9.0. |
Referenced relations describe the submitted statement, including relations in joins and subqueries. They aren't a complete record of indirect access through functions or views, and they don't identify the object that caused a denial. An absent attribute means relation information wasn't captured. An empty list means the captured statement named no relations.
The denial class, function name, message, and SQLSTATE apply to PostgreSQL privilege errors. They aren't recorded for decisions rejected before execution, such as a missing purpose role or a caller who isn't a member of that role. See Decision records for interpreting the outcome.
Tracing SQL queries
Set on any span that executes SQL:
| Attribute | Description |
|---|---|
db.query.text | The SQL statement(s) executed during the traced call. Bound parameter values are included only when aidb.otel_capture_query_parameters is on (see Configuring AIDB parameters). |
Tracing pipelines, semantic KBs, and models
These spans cover pipeline operations, semantic search, model inference and maintenance, data processing, and volume access.
| Attribute | Description |
|---|---|
aidb.pipeline.name | Pipeline name, on pipeline CRUD and execution spans. |
aidb.pipeline.id | Pipeline ID, on background worker batch-processing spans. |
aidb.semantic_kb.name | Semantic knowledge base name. |
aidb.model.name | Model name, on model inference and maintenance spans. |
aidb.model.provider | The model provider or adapter used for the call. |
aidb.hcp.model_name | Model name used by the remote service. It can differ from AIDB's registered model name. |
aidb.semantic_alias.name | Named alias over one or more semantic knowledge bases. |
aidb.search.top_k | Requested result count for a semantic knowledge base search. |
aidb.search.max_results | Requested result count for a memory-search tool call. |
aidb.volume.name | Volume targeted by a file operation. |
aidb.volume.file_name | File targeted by a read, write, or delete operation within the volume. |
Recording memory activity
Set on spans for memory and memory-session operations:
| Attribute | Description |
|---|---|
aidb.memory.name | Named memory instance used by the call. |
aidb.memory.provider | Configured memory runtime, when one is configured. |
aidb.memory.namespace | Namespace for a memory session or message operation. |
aidb.memory.actor_id | User or agent identity associated with the memory session. |
aidb.memory.session_id | Memory session read or written by the operation. |
aidb.memory.session_closed | Whether the session was already closed when a close operation was requested. |
aidb.memory.turns | Number of conversation turns in the memory session at initialization or read time. |
aidb.memory.limit | Item or character limit applied to a session read. |
aidb.memory.items | Number of items returned by a session read. |
aidb.memory.overflow_chars | Number of characters removed to stay within the read's character limit. |
aidb.memory.compacted_entries | Number of entries combined into a summary during a session read or close. |
aidb.memory.warnings | Number of non-fatal warnings during a session read. |
aidb.memory.compacting | Whether the session read triggered a model call to summarize memory. Also appears on memory metrics when applicable. |
Recording memory metrics
The aidb.memory.session.operations counter counts completed memory operations. The aidb.memory.session.operation.duration histogram records their duration in seconds. Their data points carry these attributes:
| Attribute | Description |
|---|---|
aidb.memory.operation | session_get, session_start, or session_end. |
aidb.memory.outcome | ok or warning. Failed operations aren't included in these metrics. |
aidb.memory.compacting | Whether a session read triggered a model call to summarize memory. Absent for operations where this doesn't apply, such as opening or closing a session. |
Recording registry and catalog activity
Set on spans for catalog discovery and purpose or plugin management:
| Attribute | Description |
|---|---|
aidb.catalog.name | Name of the catalog object being inspected. |
aidb.catalog.schema | Schema searched by the catalog discovery call. |
aidb.catalog.object_type | Type of object, such as a table, view, function, or sequence. |
aidb.purpose.name | Purpose being created or read. |
aidb.purpose.role | Postgres role assigned to a purpose when it is created. |
aidb.agent_plugin.name | Agent plugin being created, updated, or deleted. Added in AIDB 7.9.0. |
Recording failures as logs
Failure log records can carry an error attribute containing the error text. This is separate from the span's error.type classification. When the log is emitted within a span, its trace and span IDs link it to that operation. Error text can contain sensitive data; restrict read access to the telemetry accordingly.